1

These two questions, here and here, are similar but not the same.

Assume an element like this that links to an external SVG image:

<div style="background-image: url("/images/tags/icon/star.svg");"></div>

If the image is rendered already as a background image, what are the options for converting this SVG image into inline SVG (elsewhere in the document)?

Is the only option to reload the external file as suggested in this answer?

The goal is to skip loading the SVG file again if possible.

Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • 1
    Images should be cached by browser. It doesnt matter how many times you use image on the page. If headers are properly set, its loaded only once. Its how placeholders work.. – bigless Mar 14 '19 at 01:56
  • @bigless understood, but does this mean the only option is using AJAX as suggested by the linked answer? – Crashalot Mar 14 '19 at 02:03
  • Do you want to change color or so? I commented your goal - loading file only once – bigless Mar 14 '19 at 02:11
  • @bigless yes, change color so need to inline SVG element. what's the best way to do this? – Crashalot Mar 14 '19 at 02:29
  • No there are many other solutions, but yes, ajax is the simplest if at the end you want it to be part of your DOM. Now, you may also prefer to have it *sand-boxed*, in this case you'll probably want to use an – Kaiido Mar 14 '19 at 04:04
  • @Kaiido could you post as an answer please? thanks! – Crashalot Mar 14 '19 at 05:09

1 Answers1

2

There are other solutions, but AJAX is indeed the simplest if you plan in appending the SVG in your document.
But, beware this may not be a great idea.
As you probably know, SVG are documents, and their nodes can have id attributes. id attributes must be unique per document, so if you append twice the same svg document containing nodes with id, you will have duplicate ids and probably problems.

const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
  <defs>
    <rect id="rect1" x="0" y="0" width="50" height="50" fill="red"/>
  </defs>
  <use xlink:href="#rect1" stroke="green"/>
</svg>`;

function appendSVGDoc(markup, parent) {
  const doc = (new DOMParser).parseFromString(markup, 'image/svg+xml');
  const docEl = document.adoptNode(doc.documentElement); parent.appendChild(docEl);
}
// append a first version
appendSVGDoc(svg, document.body);
onclick = e => {
  // let's do some modifs
  document.getElementById('rect1').setAttribute('fill', 'yellow');
  onclick = e => {
    // append an other one
    appendSVGDoc(svg, document.body);
    // guess what color is the rect here?
  };
};
<p>click to modify the svg element.</p>
<p>click again to add a new copy of the original svg</p>

An other example? If your svg document contains a stylesheet, then the rules of this stylesheet will impact your whole document:

const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="200">
  <style>
    :root {
      font: 40px Serif;
    }
    rect {
      fill: green;
    }
  </style>
  <text y="50">I'm a big text in an svg image.</text>
  <rect x="25" y="75" width="50" height="50"/>
</svg>`;

function appendSVGDoc(markup, parent) {
  const doc = (new DOMParser).parseFromString(markup, 'image/svg+xml');
  const docEl = document.adoptNode(doc.documentElement); parent.appendChild(docEl);
}
onclick = e => {
  appendSVGDoc(svg, document.body);
};
<p>I am a simple html text content, and here is a simple svg &lt;rect> that should be red: <svg width="50" height="50"><rect x="0" y="0" height="50" width="50" fill="red"/></svg></p>

<p> click to append the external SVG </p>

So to avoid such cases, I can only advise you actually load your svg docs in <iframe>. From there, you will be able to access their content-document and do the edits as you wish, while keeping their document sand-boxed from your main document.
Note: <object> or <embed> seem more logical elements than <iframe>, but, for a reason still unknown by me, no browser does use the cache when fetching resources for such elements. That's pretty bad...
However, while <iframe> do use cache, they don't resize dynamically, and they come with ugly borders...

function appendSVGDoc(blob, parent) {
  return new Promise((res, rej) => {
    const url = URL.createObjectURL(blob);
    const iframe = document.createElement('iframe');
    iframe.classList.add('svg-viewer');
    iframe.onload = e => {
      const doc = iframe.contentDocument;
      const docEl = doc.documentElement;
      // iframe doesn't auto size like <object> would
      iframe.width = docEl.getAttribute('width') || '100%';
      iframe.height = docEl.getAttribute('height') || '100%';
      res(doc);
    };
    iframe.onerror = rej;
    iframe.src = url;
    parent.appendChild(iframe);
  });
}

fetch(url)
  .then(resp => resp.blob())
  .then(blob => appendSVGDoc(blob, document.body));
.then(svgdoc => {
    // here manipulate the svg document
  })
  .catch(console.error);

StackSnippets® overly protected (null-origined) own blocking us from accessing inner-iframes content, I had to out-source this last example to jsfiddle.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Hi @Kaiido! Sandboxing is ideal, but the iFrame approach yields a cross-origin error: https://stackoverflow.com/questions/55192060/blocked-from-accessing-svg-document-rendered-with-data-uri-accessing-a-cross-or. Do you have any suggestions? – Crashalot Mar 15 '19 at 23:54
  • @Crashalot here I am not using dataURLs, just copy either the code here or the one in the fiddle? – Kaiido Mar 16 '19 at 00:08
  • OK thanks, didn't realize using a blob made a difference! Will try, thanks so much for your help! – Crashalot Mar 16 '19 at 00:15