2

I'm using CSS transitions to animate the position of SVG elements.

However, I've discovered that IE does not seem to support this:

SVG animation is not working on IE11

Here's a simple example that works in Chrome and Safari, but not IE (neither IE11 nor Edge) nor Firefox

document.getElementById("button").addEventListener("click", moveRect, false);

function moveRect() {

  var rect = document.getElementById("rect");
  rect.setAttributeNS(null, "y", 300);

}
button {
  display: block;
  margin: 1em 0;
}

svg {
  border: 1px solid #ccc;
}

rect {
  fill: blue;
  transition: all 700ms ease-in-out;
}
<button id="button">Move Rect</button>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
  <rect id="rect" x="0" y="0" height="100" width="100%"></rect>
</svg>

So, my plan is to use CSS transitions for browsers where that is supported, and fall back to JavaScript animation where it is not.

My problem is, how do I detect when that is the case?

I guess I can detect IE and Firefox, but I've always here that you should detect features, not browsers.

Thanks in advance!

mattstuehler
  • 9,040
  • 18
  • 78
  • 108
  • 1
    Have you checked https://modernizr.com/ ? – Xotic750 Apr 12 '18 at 20:27
  • [how-to-detect-html-5-compatibility-in-browser](https://stackoverflow.com/questions/6731303/how-to-detect-html-5-compatibility-in-browser) – Taki Apr 12 '18 at 20:48
  • 1
    Use SMIL and [fakeSmile](https://leunen.me/fakesmile/) instead perhaps – Robert Longson Apr 12 '18 at 20:48
  • @Xotic750 - modernizr will detect whether the browser supports CSS transitions *generally*. However - my issue is whether the browser supports them on *SVG elements*. E.g., MS Edge supports CSS transitions on most DOM elements (e.g., `div`), but does not support them on an SVG `rect`. Unless I'm mistaken, modernizr can't help with this. – mattstuehler Apr 17 '18 at 15:52
  • @RobertLongson - thank you for your suggestion. However, my understanding is that SMIL is being deprecated, so I'm reluctant to use it. I am comfortable using JavaScript instead, but my understanding is that, in general, CSS transitions are more performant than JavaScript, so I'd like to use them when possible, and *fall back* to JavaScript when they're not available. So, my question is really about figuring out when I need to fall back, not what to use *when* I fall back. – mattstuehler Apr 17 '18 at 15:57
  • 1
    Your understanding is wrong, SMIL is not being deprecated. CSS and SMIL is generally more performant than javascript. There is a javascript SMIL polyfill but I don't know of a javascript CSS animation polyfill – Robert Longson Apr 17 '18 at 16:04
  • 1
    Sorry I haven't looked into very much, but a quick google search came back with this https://github.com/danmoore83/modernizr-svg-transforms – Xotic750 Apr 17 '18 at 16:09
  • @RobertLongson - here's why I thought SMIL was being deprecated: https://www.sarasoueidan.com/blog/state-of-svg-animation/ – mattstuehler Apr 19 '18 at 12:01
  • @Xotic750 - Thank you for that link - exactly what I needed. If you make that an answer, I'd mark it as the accepted answer. – mattstuehler Apr 19 '18 at 12:03
  • Don't believe everything you read on the web. – Robert Longson Apr 19 '18 at 13:22
  • Thanks, but a link isn't really an answer. :) – Xotic750 Apr 19 '18 at 20:28

2 Answers2

2

Since you are using js anyway i would suggest to use svgjs, this will work in all browsers. It also is an more appropriate way of animation svg. It also has some realy nice other features.

You may want to take a look at: http://svgjs.dev/animating/

Nice example for your case:

var canvas = SVG('drawing').size('100%', '100%').viewbox(0,0,800,1000)
  , rect = canvas.rect(100, 100)
  , path = canvas.path("m 357.64532,453.84097 c 17.62007,8.02216 -2.12058,27.70935 -13.33334,29.28571 -30.3859,4.27185 -48.34602,-29.97426 -45.23807,-55.9524 5.5594,-46.46879 56.1311,-70.59787 98.57145,-61.19043 62.28294,13.8058 93.32728,82.57702 77.1428,141.19051 C 453.21679,585.29693 365.67122,623.42358 290.97859,600.26951 196.98554,571.13248 151.71003,464.56996 181.93108,373.84089 218.53281,263.95583 344.23687,211.49702 450.97875,248.84102 576.77037,292.84963 636.43303,437.76771 591.93099,560.50775 540.55162,702.21597 376.3736,769.09583 237.6452,717.41234 80.01319,658.68628 5.9069261,475.21736 64.788247,320.50751 130.84419,146.94643 333.62587,65.607117 504.31214,131.69819 693.80625,205.0718 782.38357,427.18225 709.07382,613.84113")
  , length = path.length()
  
path.fill('none').stroke({width:1, color: '#ccc'})

rect.animate(8000, '<>').during(function(pos, morph, eased){
    var p = path.pointAt(eased * length)
    rect.center(p.x, p.y)
}).loop(true, true)
html, body, #drawing{
  width:100%;
  height:100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/svg.js/2.5.0/svg.js"></script>
<div id="drawing"></div>

Credits: http://jsfiddle.net/dotnetCarpenter/vceakozz/?utm_source=website&utm_medium=embed&utm_campaign=vceakozz

wout
  • 2,477
  • 2
  • 21
  • 32
Jop Knoppers
  • 676
  • 1
  • 10
  • 22
  • thank you for your answer. But I'm curious - why is JavaScript a more appropriate way to animate SVG? In my experience - JavaScript animation is typically much less performant/smooth than CSS transitions (since CSS transitions are typically handled by the GPU). CSS animations also seem much smoother on mobile devices.So, I generally prefer CSS over JavaScript whenever it's available. Is this incorrect? – mattstuehler Apr 13 '18 at 15:43
  • 1
    Javascript animations can be very fast! Its always the backgound code that makes things fast or slow. Soon svg.js 3.0 will be out and its animations are even more performant than the old ones (we are talking about a real boost here) but even in 2.0 they are fast enough. Here you have a tut building a pong game with svg.js: https://css-tricks.com/pong-svg-js/. And here you got another fancy animation: https://jsfiddle.net/Fuzzy/509s9kum/1/ – Fuzzyma May 03 '18 at 08:25
1

The premise is not entirely correct: at least for Firefox, this is not CSS transitions which doesn't work, but recognizing x and y attributes as presentational attributes and hence as CSS settable and hence as CSS transition-able.

This wasn't the case in SVG1.1 and has still to be implemented in FF and IE, but SVG1.1 CSS transitions do work in FF.

To feature-detect x and y, you could simply check if an SVGElement has these properties available on its style property.

var supportSVG2XYasCSS = 
  document.createElementNS("http://www.w3.org/2000/svg", 'foo')
    .style.x !== undefined;

console.log("Your browser does%s support setting x and y attributes through CSS" +
  " and thus probably%s CSS transitions on it.",
  supportSVG2XYasCSS ? '' : "n't",
  supportSVG2XYasCSS ? '' : ' neither does it support');

But, HTMLElements do have a height property on their style, and Firefox, IE and Edge will set it to SVGElement regardless it shouldn't be there.
So you can't feature detect these attributes by another mean than checking if the BBox of the element you did set these properties on has changed:

var prev = elem.getBBox();
elem.setAttribute('class', 'trig');
var next = elem.getBBox();
console.log({
  width: prev.width !== next.width,
  height: prev.height !== next.height,
  x: prev.x !== next.x,
  y: prev.y !== next.y
});
#elem{
  width: 10px;
  height: 10px;
  x: 0;
  y: 0;
}
#elem.trig{
  width: 100px;
  height: 100px;
  x: 10;
  y: 10;
}
<svg>
  <rect id="elem"/>
</svg>

Now let's get back at detecting CSS transitions available on SVG.

So as said previously, Firefox does support all SVG1.1 transitions, but not the SVG2 ones, Edge does support some SVG1.1 transitions, and IE supports none.

So to feature detect it, you would have to check every properties on their own, and to check a transition property you can set up something like this:

  • set initial value
  • get computed value (initial state)
  • set destination value
  • trigger a reflow so the new values are computed and the transition can kick in
  • get computed value (transition state)

If the transition is working, the computed values in the transition state should be the same as the ones in the initial state, if not supported, the destination value would be set directly.

But, as stated in the first part, some HTMLElement's CSSproperties will leak on our tests and would thus require some more work, that I will let for the readers.
Note that arguably, these are not "false positives" since browsers will indeed trigger a transition on them, they will simply have no visual effect on the element...

function testCSSTransitionsOnSVG() {
  // all the properties we'll test
  var props = {
// stable(?) ones  
    'fill': {initial: 'rgba(255, 255, 255, 1)', after: 'rgba(0, 0, 0, 1)'},
    'stroke-dasharray': {initial: '1, 1', after: '1000, 1000'},
    'stroke-dashoffset': {initial: '0%', after: '100%'},
    'stroke-width': {initial: '1', after: '5'},
    'stroke': {initial: 'rgba(0, 0, 0, 1)', after: 'rgba(255, 255, 255, 1)'},
    'x': {initial: '0px', after: '100px'},
    'y': {initial: '0px', after: '100px'},
// false positives in IE & Edge
    'transform': {initial: 'matrix(1,0,0,1,0,0)', after: 'matrix(25,1.2,1.2,25,42,42)'},
    'transform-origin': {initial: '0px 0px', after: '100px, 100px'},
// false positives in FF & IE & Edge
    'width': {initial: '0px', after: '100px'},
 'height': {initial: '0px', after: '100px'}
  }, key;
  // set up our testing elements
  var SVGns = 'http://www.w3.org/2000/svg',
    parent = document.createElementNS(SVGns, 'svg');
  parent.setAttribute('class', '__tester__');

  var style = document.createElementNS(SVGns, 'style');
  // write up our initial CSS rules using a strict selector
  style.textContent += 'body > .__tester__ > rect{transition:all 10s linear;';
  for (key in props) {
    style.textContent += key + ':' + props[key].initial + ';';
  }
  // the ones we should animate to
  style.textContent += '}\n body > .__tester__.trig > rect{';
  for (key in props) {
    style.textContent += key + ':' + props[key].after + ';';
  }
  style.textContent += '}';
  parent.appendChild(style);
  // our actual element to be tested
  var elem = document.createElementNS(SVGns, 'rect');
  elem.setAttribute('width', '100%');
  elem.setAttribute('height', '100%');
  parent.appendChild(elem);
  // now let's start the real checks
  document.body.appendChild(parent);
  parent.offsetWidth; // force a reflow
  // get the initial computed values
  var results = {},
    computed = getComputedStyle(elem);
  for (key in props) {
    results[key] = computed[key];
  }
  // set the new class
  parent.setAttribute('class', '__tester__ trig');
  parent.offsetWidth; // force a reflow
  computed = getComputedStyle(elem);
  var support = {};
  for (key in props) {
    support[key] = results[key] === computed[key] && computed[key] !== undefined;
  }
  document.body.removeChild(parent);
  return support;
}

console.log(testCSSTransitionsOnSVG());
Kaiido
  • 123,334
  • 13
  • 219
  • 285