Search code examples
javascriptsvg

SVG+JavaScript, how to find out if <use> reference exists


I have an SVG file like this

<svg class="icon" viewBox="0 0 16 16">
  <use href="assets/icons.svg#my-fancy-icon"></use>
</svg>

Using JavaScript, how do I find out if the href attribute of use element points to an element that actually exists?


Solution

  • Get the <use> element's boundaries: width & height: 0 = not existent

    When a svg element is successfully appended it will return a width and height value > 0 (calling getBBox()).

    If not – getBBox() will return a width and height value of 0.
    The use reference is not valid/existent. This also applies to elements that are incorrectly appended e.g when using document.createElement() (missing the correct namespace) instead of document.createElementNS().

    Example 1: check width and height

    let useEls = document.querySelectorAll('use');
    
    useEls.forEach(function(use) {
      let bb = use.getBBox();
      let [width, height] = [bb.width, bb.height];
      if (width == 0 && height == 0) {
        use.closest('svg').classList.add('notavailable')
      }
    })
    svg {
      height: 10em;
      border: 1px solid #ccc;
      display: inline-block;
    }
    
    .notavailable {
      border: 1px solid red;
    }
    <svg id="svgIcons" class="svgIcons" viewBox="0 0 100 100" style="position:absolute; height:0; width:0;" xmlns="http://www.w3.org/2000/svg">
      <symbol id="home" viewBox="0 0 34 48">
        <path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
      </symbol>
    </svg>
    
    <svg viewBox="0 0 34 48">
      <use href="#home" />
    </svg>
    <svg viewBox="0 0 34 48">
      <use href="#notExistent" />
    </svg>

    Example 2: clone all use elements in temporary svg

    This way we can also check invisible <use> elements hidden by display: none that would be overlooked by the previous checking method.

    checkUseEls();
    
    function checkUseEls() {
      // collect missing references
      let missingRefs = [];
      //add temporary hidden svg
      let svgTmp = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      svgTmp.setAttribute('style', 'position:absolute; width:0; height:0;visibility:hidden');
      document.body.appendChild(svgTmp);
    
      //add cloned use els
      let useEls = document.querySelectorAll('use');
      useEls.forEach(function(use) {
        let cloned = use.cloneNode();
        cloned.setAttribute('style', 'display:block!important')
        svgTmp.appendChild(cloned)
        let bb = cloned.getBBox();
        let [width, height] = [bb.width, bb.height];
        if (width == 0 && height == 0) {
          missingRefs.push(cloned.getAttribute('href'))
        }
      })
      svgTmp.remove();
      console.log(missingRefs)
    
    }
    svg {
      height: 10em;
      border: 1px solid #ccc;
      display: inline-block;
    }
    
    .notavailable {
      border: 1px solid red;
    }
    <svg id="svgIcons" class="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
      <symbol id="home" viewBox="0 0 34 48">
        <path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
      </symbol>
      <symbol id="homeHidden" viewBox="0 0 34 48">
        <path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
      </symbol>
    </svg>
    
    <svg viewBox="0 0 34 48">
      <use href="#home" />
    </svg>
    <svg viewBox="0 0 34 48" style="display:none">
      <use href="#notExistent" />
    </svg>
    <svg viewBox="0 0 34 48">
      <use href="#homeHidden" style="display:none"/>
    </svg>

    Symbol #homeHidden is existent but hidden. By applying display:block to it's cloned instance, we can check it's with/height.

    Example 3: query use definitions

    You may also search for all definitions e.g <symbol> elements

    findMissingUseDefs();
    
    function findMissingUseDefs() {
      // collect missing references
      let missingRefs = [];
    
      //add cloned use els
      let useEls = document.querySelectorAll("use");
      useEls.forEach((use) => {
        let href = use.getAttribute("xlink:href") ?
          use.getAttribute("xlink:href") :
          use.getAttribute("href");
        let id = href.replace("#", "");
        let defs = document.querySelectorAll(`${href}`);
        if (!defs.length) {
          missingRefs.push(href);
        }
      });
      console.log(missingRefs);
    }
    svg {
      height: 10em;
      border: 1px solid #ccc;
      display: inline-block;
    }
    
    .notavailable {
      border: 1px solid red;
    }
    <svg id="svgIcons" class="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
          <symbol id="home" viewBox="0 0 34 48">
            <path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
          </symbol>
          <symbol id="homeHidden" viewBox="0 0 34 48">
            <path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
          </symbol>
        </svg>
    
    <svg viewBox="0 0 34 48">
          <use href="#home" />
        </svg>
    <svg viewBox="0 0 34 48" style="display:none">
          <use href="#notExistent" />
        </svg>
    <svg viewBox="0 0 34 48">
          <use href="#homeHidden" style="display:none"/>
        </svg>

    If you're using external svgs you would need to fetch all these files and query the parsed file. All in all checking bboxes is probably the easiest way.