Home > Enterprise >  SVG animation triggering on load rather than on DOM insertion
SVG animation triggering on load rather than on DOM insertion

Time:09-29

The code below animates a SVG circle changing color and works as expected.

If the call to SVG.addAnimatedCircle(this.root) is made from within the callback method (instead of where it is below, inside the constructor), the animation starts when the document is loaded — and is therefore invisible unless the window is clicked — rather than when the event is triggered.

class SVG {

    constructor() {
        const root = document.createElementNS(
            'http://www.w3.org/2000/svg', 'svg');
        root.setAttribute('viewBox', '-50 -50 100 100');
        this.root = root;

        this.callback = this.callback.bind(this);
        window.addEventListener('click', this.callback);

        SVG.addAnimatedCircle(this.root);
    }

    callback() {
        // SVG.addAnimatedCircle(this.root);
    }

    static addAnimatedCircle(toElement) {
        const el = document.createElementNS(
            'http://www.w3.org/2000/svg', 'circle');
        el.setAttribute('cx', '0');
        el.setAttribute('cy', '0');
        el.setAttribute('r', '10');
        el.setAttribute('fill', 'red');

        toElement.appendChild(el);

        const anim = document.createElementNS(
            'http://www.w3.org/2000/svg', 'animate');
        anim.setAttribute('attributeName', 'fill');
        anim.setAttribute('from', 'blue');
        anim.setAttribute('to', 'red');
        anim.setAttribute('dur', '3s');

        el.appendChild(anim);  
    }   
    
}

const svg = new SVG();
document.body.appendChild(svg.root);

(The above doesn't need to be inside a class of course, I'm simplifying a more complex class).

Why is that? Isn't the animation supposed to start when the element is created and added to the DOM?

CodePudding user response:

The <animate> element you create will have its begin attribute computed to 0s (since unset).
This 0s value is relative to the "document begin time", which itself in this HTML document corresponds to the root <svg>'s current time.

This means that if you do create such an <animate> element after its root <svg> element has been in the DOM, its animation state will depend on how long the root <svg> element has been in the DOM:

We can set the <svg>'s current time trough its SVGSVGElement.setCurrentTime() method.
So to create an <animate> that would start at the time it got created, no matter when it is, we could use this, however, this will also affect all the other <animate> that are already in the <svg>:

So, while it may do for some users, in most cases it's probably better to instead set only the <animate>'s begin attribute.
Luckily, we can also get the current time, with the SVGSVGElement.getCurrentTime() method.

But the way we usually do this is to use the API fully and control it all through JS, since you already did start using JS.
To do so, we set the begin attribute to "indefinite", so that it doesn't start automatically, and then we call the SVGAnimateElement (<animate>)'s beginElement() method, which will start the animation manually, when we want:

CodePudding user response:

(Not the answer, just presentation of the problem for possible accomodation into the question and/or proper answer suggested by OP in their comment.

<style>svg {outline: #0FF6 solid; outline-offset: -2px;}</style>

<table role="presentation" border><tr><td>

1. Static SVG with animated circle:

<td>

<svg viewBox="-50 -50 100 100" width="30" height="30">
 <circle r="40" fill="black">
  <animate begin="0.5s" fill="freeze" attributeName="fill" from="blue" to="red" dur="5s"></animate>
 </circle>
</svg> 

<tr><td>

2. Empty SVG, 

<button onclick='
 emptySVG.innerHTML = document.querySelector("circle").outerHTML;
'>Put similar animated circle into it</button>:

<td>

<svg id="emptySVG" viewBox="-50 -50 100 100" width="30" height="30">
 <!-- empty -->
</svg>

<tr><td>

3. <button onclick='
 sampleCell.innerHTML = document.querySelector("svg").outerHTML
'>Create new SVG with animated circle</button>:

<td id="sampleCell">
(here.)

</table>

<p>
<button onclick="location.reload()">Reload page</button>
<button onclick="
[...document.querySelectorAll('animate')].forEach(a=>{
  //a.setAttribute('begin','indefinite'); // does not seem to be necessary
  a.beginElement();
 })
">Reset all animations</button>

Putting animated circle to second SVG produces state that corresponds with already elapsed duration in existing empty SVG: it matches the first one, so it either runs in sync or is finished. Goal is to run the animation upon circle's appearance.

  • Related