glemiere
glemiere

Reputation: 5024

How can I animate a SVG who is not hard coded?

I can't animate a svg that I import using the img tag. I can't hard code the svg, as my project generates it during pre-processing using webpack. Sadly, it doesn't seems like I could fetch my file through the svg tag, as I'm not aware of any "src" or "href" attribute.

How can I animate a SVG who is not hard coded ?

Upvotes: 0

Views: 193

Answers (1)

Kaiido
Kaiido

Reputation: 136766

That really depends on how is powered your animation.


There are basically three ways to generate an animated SVG:

  • SMIL Animation - unfortunately not really widely supported (but you did tag with [svg-animate], so let's take it as the first case).
  • CSS Animation - With the emergence of SVG2, I bet these will become more and more common.
  • Script based Animations.

How do these behave when their <svg> documentElement is embedded in an <img> tag?


SMIL animations in <img>.

In supporting browsers, these would run just normally, as if your SVG was not embedded.
The only restrictions you would face are

  • You wouldn't receive any user-gesture, so the element.click and alike events won't work
  • You can't fallback to a script based animation for browsers that don't support SMIL animations (IE...).

Both limitations do not affect SVG loaded in <object>, <embed> or <iframe>, so you could well use it instead if you need it.

var svgStr = `
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50">
    <rect id="rect" x="-30" y="0" width="30" height="50">
      <!-- this will work normally, even in an <img> -->
      <animate attributeType="XML" attributeName="x" from="-30" to="50"
        begin="0s" dur="10s" repeatCount="indefinite"/>
      <!-- this will not work in <img>, but will in <object>/<iframe>/<embed> -->
      <animate attributeType="XML" attributeName="fill" from="blue" to="red"
        begin="rect.click"
        dur="1s" repeatCount="1"/>
    </rect>
    <!-- js-based workaround won't work in <img> but will in <object>/<iframe>/<embed> -->
    <script src="https://cdn.rawgit.com/FakeSmile/FakeSmile/23c5ceae/smil.user.js"><\/script>
  </svg>`;

loadSVG(document.images[0]);
loadSVG(document.querySelector('object'));

function loadSVG(container) {
  var url = URL.createObjectURL(new Blob([svgStr], {type: 'image/svg+xml'}));
  container.src = container.data = url;
}
img{
  border: 1px solid green;
}
object{
  border: 1px solid blue;
}
<img src="">
<object></object>
<div>Try to click the black rectangle in both the <code>&lt;img></code> and <code>&lt;object></code> tags.

CSS Animations in <img>.

Just like SMIL animations, they should work in supporting browsers, with the same user-gesture limitations, and the same possible ways around (use an other container):

var svgStr = `
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50">
    <rect id="rect" x="0" y="0" width="30" height="50"/>
    <defs>
      <style>
        #rect {
          /* this will work normally, even in an img */
          animation: move 10s linear infinite;
        }
        #rect:hover {
          /* this will not work in img, but will in object/iframe/embed */
          animation: move 10s linear infinite, color 1s 1;
        }
        @keyframes move {
          from {
            transform: translate(-30px, 0px);
          }
          to {
            transform: translate(50px, 0px);
          }
        }
        @keyframes color {
          from {
            fill: blue;
          }
          to {
            fill: red;
          }
        }
      </style>
    </defs>
  </svg>`;

loadSVG(document.images[0]);
loadSVG(document.querySelector('object'));

function loadSVG(container) {
  var url = URL.createObjectURL(new Blob([svgStr], {type: 'image/svg+xml'}));
  container.src = container.data = url;
}
img{
  border: 1px solid green;
}
object{
  border: 1px solid blue;
}
<img src="">
<object></object>
<div>Try to mouse hover the black rectangle in both the <code>&lt;img></code> and <code>&lt;object></code> tags.

Script based Animations in <img>.

These will simply not work. SVG documents embedded in <img> tag can not be scripted. To workaround this, use either an <object>, an <embed> or an <iframe> element as container.

var svgStr = `
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50">
    <rect id="rect" x="0" y="0" width="30" height="50"/>
    <script type="application/javascript">  
      // will simply never work in img
      var x = 0, rect = document.getElementById('rect');
      function anim() {
        x = (x + 1) % 80;
        rect.setAttribute('x', x - 30);
        requestAnimationFrame(anim);
      }
      anim();
    <\/script>
  </svg>`;

loadSVG(document.images[0]);
loadSVG(document.querySelector('object'));

function loadSVG(container) {
  var url = URL.createObjectURL(new Blob([svgStr], {type: 'image/svg+xml'}));
  container.src = container.data = url;
}
img{
  border: 1px solid green;
}
object{
  border: 1px solid blue;
}
<img src="">
<object></object>


So basically, SVG in <img> comes with a lot of restrictions, that can all be overwhelmed by using an other container.
Now, every container will come with its own restrictions:

  • <iframe> will not resize to its content, it will also come by default with a border and some other ugly things.
  • <object> and <embed> will get unloaded by webkit browsers when not visible (display: none), and won't be cached in any browser...

And of course, there is also the possibility to fetch your SVG markup through AJAX and to load it inside your actual HTML page, but I personally can't advice to do so:

  • You would need to be sure you don't have duplicate id elements,
  • You would need to be sure all your CSS rules are specific enough that they won't affect other elements in your page,
  • You would have to be sure you are loading only trusted code, since scripts will run, that is to say you keep a wide open door to XSS attacks.

And since we're here, an other limitation of SVG in <img> is that it can not load any resources outside its own markup, everything needs to be included in it directly, even fonts and raster images.

Upvotes: 2

Related Questions