How to reset an svg animation to its initial state? - javascript

I have an SVG animation similar to this:
function startAnimation() {
document.querySelector('#anim-width').beginElement();
}
<svg width="100" viewBox="0 0 100 100">
<rect id="box" x="10" y="10" fill="red" width="10" height="10">
<animate
id="anim-width"
attributeName="width"
from="10"
to="80"
dur="1s"
fill="freeze"
begin="click"
/>
<animate
attributeName="height"
from="10"
to="80"
begin="anim-width.end + 0s"
dur="1s"
fill="freeze"
/>
</rect>
</svg>
<br/>
<button onclick="startAnimation()">Start</button>
What I want to achieve is the red box starts from 10 by 10, when clicking the button, the width expands from 10 to 80, then the height expands to 80 after the width animation is done.
It works fine for the first playback, but when clicking the button again the height starts from 80 instead of 10, how do I reset everthing to its intial state and replay the entire animation?
I tried adding document.querySelector('#box').setAttribute('height', '10'); in the startAnimation() function but it doesn't seem to work.

I'm adding an element <set> inside the rect and I'm starting the set element inside the function function startAnimation()
The <set> element ìs a maner of setting the value of an attribute (height in this case), like an animation with duration 0.
function startAnimation() {
document.querySelector("#set").beginElement();
document.querySelector("#anim-width").beginElement();
}
<svg width="100" viewBox="0 0 100 100">
<rect id="box" x="10" y="10" fill="red" width="10" height="10">
<animate id="anim-width" attributeName="width" from="10" to="80" dur="1s" fill="freeze" begin="click" />
<animate id="anim-height" attributeName="height" from="10" to="80" begin="anim-width.end + 0s" dur="1s" fill="freeze" />
<set id="set" attributeName="height" to="10"></set>
</rect>
</svg>
<br />
<button onclick="startAnimation()">Start</button>

Another option is just to have an <animate> element that starts the animation by resetting the height.
document.querySelector("#anim-height").addEventListener("endEvent", enableButton);
function startAnimation() {
document.querySelector("#start-btn").disabled = true;
document.querySelector("#anim-start").beginElement();
}
function enableButton()
{
document.querySelector("#start-btn").disabled = false;
}
<svg width="100" viewBox="0 0 100 100">
<rect id="box" x="10" y="10" fill="red" width="10" height="10">
<animate id="anim-start" attributeName="height" to="10" dur="0.01s" fill="freeze" begin="indefinite" />
<animate id="anim-width" attributeName="width" from="10" to="80" dur="1s" fill="freeze" begin="anim-start.end + 0s" />
<animate id="anim-height" attributeName="height" from="10" to="80" begin="anim-width.end + 0s" dur="1s" fill="freeze" />
</rect>
</svg>
<br />
<button id="start-btn" onclick="startAnimation()">Start</button>

Related

NS_BINDING_ABORTED with svg images only

I have a simple JavaScript code that generating waiting load spinner when a captcha image refreshing. The script works fine with gif images but with animated svg images, the loading spinner does not work and in FireFox returns NS_BINDING_ABORTED in the browser's network console. Here is the code:
$(document).ready(function(){
$("#captchaImg").click(function(e){
e.preventDefault();
src = $(this).children('img').attr('src');
width = '{{config('captcha.flat.width')}}px';
height= '{{config('captcha.flat.height')}}px';
console.log(width, height)
// IN THE FOLLOWING LINE, REPLACING gif with svg makes the error.
$(this).children('img').attr({'src':'/imgs/loading.gif','width':width, 'height': height})
src = src.replace(/&t=.*/,'')
t = new Date();
$(this).children('img').attr('src',src+"&t="+t.getTime());
$( "#randQuote" ).text( '{{__('Loading')}}' );
$.ajax({
url: "/rand-quote",
cache: false
})
.done(function( data ) {
console.log(data.msg)
$( "#randQuote" ).text( data.msg );
return false;
});
})
})
I don't know what is the difference in this case between gif and svg that causes that issue? and how could I solve it?!
Note:
The svg image used is generated from https://loading.io and the following its code:
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="64px" height="64px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g transform="rotate(0 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#482173">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.875s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(45 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#2e6f8e">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(90 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#29af7f">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.625s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(135 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#bddf26">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(180 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#482173">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.375s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(225 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#2e6f8e">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(270 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#29af7f">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.125s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(315 50 50)">
<rect x="37.5" y="5" rx="4.76" ry="4.76" width="25" height="34" fill="#bddf26">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
</rect>
</g>
<!-- [ldio] generated by https://loading.io/ --></svg>
I found the solution but I still don't know the reason
The solution is simply adding the loading.svg in the HTML like the following:
<img style="display:none;" src="/imgs/loading.svg">
After that replacing gif by svg in the script has worked fine. The solution is inspired form my Captcha Class # line 125. Originally, I made it to ensure loading the image during the page loading before its call from the JavaScript. I noticed that svg images works there fine.
I still do not able to identify what is the difference between gif
and svg that leads to that behavior, in which, adding the image
src of the image into page's HTML is obligatory to allow calling it
afterwords from the JavaScript?

Restart SVG animation sequence from Javascript

I'm trying to restart SVG animation sequence from Javascript. Restart-button below will broke SVG animation sequence. How to restart/reset entire sequence?
document.getElementById("button").addEventListener("click", function() {
document.getElementById("fore").beginElement();
});
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150">
<path id="track" fill="none" stroke="#000000" stroke-width="1" stroke-dasharray="10,10" d="M 50 100 L 950 50"/>
<path id="plane" d="M-10 -10 L10 0L-10 10z" fill="red" />
<animateMotion xlink:href="#plane"
id="fore"
begin="0s;back.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto"
keyPoints="0;1"
keyTimes="0;1"
><mpath xlink:href="#track" /></animateMotion>
<animateMotion xlink:href="#plane"
id="back"
begin="fore.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto-reverse"
keyPoints="1;0"
keyTimes="0;1"
><mpath xlink:href="#track" /></animateMotion>
</svg>
<button id="button">RESTART</button>
You can use setCurrentTime on the entire svg element. I added an id of svgEl to the svg node and then when rest is clicked we do:
document.getElementById('svgEl').setCurrentTime(0);
Have a look at this:
document.getElementById("button").addEventListener("click", function() {
document.getElementById('svgEl').setCurrentTime(0);
});
<svg id="svgEl" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150">
<path id="track" fill="none" stroke="#000000" stroke-width="1" stroke-dasharray="10,10" d="M 50 100 L 950 50"/>
<path id="plane" d="M-10 -10 L10 0L-10 10z" fill="red" />
<animateMotion xlink:href="#plane"
id="fore"
begin="0s;back.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto"
keyPoints="0;1"
keyTimes="0;1"
restart="always"
><mpath xlink:href="#track" /></animateMotion>
<animateMotion xlink:href="#plane"
id="back"
begin="fore.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto-reverse"
keyPoints="1;0"
keyTimes="0;1"
restart="always"
><mpath xlink:href="#track" /></animateMotion>
</svg>
<button id="button">RESTART</button>

SVG animate pattern from top left

I got this simple SVG animation that is transforming the pattern from dots to circles on page load.
<svg width="596" height="255">
<pattern id="pattern-circles" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="6" cy="6" r="3" stroke="red" stroke-width="2" fill="transparent">
<animate attributeName="r" values="0; 5" dur="2s" begin="0s" repeatCount="0" fill="freeze" />
</circle>
</pattern>
<!-- The canvas with our applied pattern -->
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern-circles)" />
</svg>
I am pretty new to SVG animation and have a hard time figure out how to achieve my goal, in addition to what I already got (dots to circles), to have this animation start/fade-in from the top left dot and end in the bottom right dot - is this even possible to achieve with this SVG pattern setup? Is there a way to isolate the dots, and stagger them in one-by-one?
Here is one idea where I will generate the circles using JS. On each iteration I increment the delay using i/j to create the to top-left to bottom-right animation. The position is trivial, the cx is based on i and cy on j
var d = 20;
var nx = 596/d; /* number of circles in a row */
var ny = 255/d; /* number of circles in a columns */
let svg = document.querySelector("svg");
for(var i=0;i<nx;i++) {
for(var j=0;j<ny;j++) {
svg.insertAdjacentHTML( 'beforeend','<circle cx="'+(6 + d*i)+'" cy="'+(6 + d*j)+'" r="0" stroke="red" stroke-width="2" fill="transparent"><animate attributeName="r" values="0; 5" dur="1s" begin="'+((i+j)/10)+'s" repeatCount="0" fill="freeze" /></circle>');
}
}
<svg width="596" height="255">
</svg>
To have a to-right animation keep only the i (same logic if you want a to-bottom one by keeping only the j)
var d = 20;
var nx = 596/d; /* number of circles in a row */
var ny = 255/d; /* number of circles in a columns */
let svg = document.querySelector("svg");
for(var i=0;i<nx;i++) {
for(var j=0;j<ny;j++) {
svg.insertAdjacentHTML( 'beforeend','<circle cx="'+(6 + d*i)+'" cy="'+(6 + d*j)+'" r="0" stroke="red" stroke-width="2" fill="transparent"><animate attributeName="r" values="0; 5" dur="1s" begin="'+(i/10)+'s" repeatCount="0" fill="freeze" /></circle>');
}
}
<svg width="596" height="255">
</svg>
An infinite animation:
var d = 20;
var nx = 596/d; /* number of circles in a row */
var ny = 255/d; /* number of circles in a columns */
let svg = document.querySelector("svg");
for(var i=0;i<nx;i++) {
for(var j=0;j<ny;j++) {
svg.insertAdjacentHTML( 'beforeend','<circle cx="'+(6 + d*i)+'" cy="'+(6 + d*j)+'" r="0" stroke="red" stroke-width="2" fill="transparent"><animate attributeName="r" values="0; 5;0" dur="2s" begin="'+((i+j)/20)+'s" repeatCount="indefinite" /></circle>');
}
}
<svg width="596" height="255">
</svg>
And why not from the center:
var d = 20;
var nx = 596/d; /* number of circles in a row */
var ny = 255/d; /* number of circles in a columns */
var ic = nx/2;
var jc = ny/2;
let svg = document.querySelector("svg");
for(var i=0;i<nx;i++) {
for(var j=0;j<ny;j++) {
svg.insertAdjacentHTML( 'beforeend','<circle cx="'+(6 + d*i)+'" cy="'+(6 + d*j)+'" r="0" stroke="red" stroke-width="2" fill="transparent"><animate attributeName="r" values="0; 5;0" dur="2s" begin="'
+( Math.sqrt((ic - i)*(ic - i)+(jc - j)*(jc - j))/20)+'s" repeatCount="indefinite" /></circle>');
}
}
<svg width="596" height="255">
</svg>
Add an animated mask with a linearGradient.
<svg width="596" height="255">
<linearGradient id="prog-mask" x1=0% x2="100%" y1="0%" y2="100%">
<stop offset="0%" stop-color="white" stop-opacity="1" />
<stop offset="5%" stop-color="white" stop-opacity="0">
<animate attributeName="offset" values="0; 1" dur="2s" begin="0s" repeatCount="0" fill="freeze" />
<animate attributeName="stop-opacity" values="0; 1" dur="2s" begin="2s" repeatCount="0" fill="freeze" />
</stop>
<stop offset="100%" stop-color="white" stop-opacity="0" />
</linearGradient>
<mask id="prog-render">
<rect x="0" y="0" width="100%" height="100%" fill="url(#prog-mask)"/>
</mask>
<pattern id="pattern-circles" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="6" cy="6" r="3" stroke="red" stroke-width="2" fill="transparent">
<animate attributeName="r" values="0; 5" dur="2s" begin="0s" repeatCount="0" fill="freeze" />
</circle>
</pattern>
<!-- The canvas with our applied pattern -->
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern-circles)" mask="url(#prog-render)"/>
</svg>
You can fade things in with linearGradients. Here, the linearGradients disappear revealing the pattern below them.
Because we have two linearGradients the animation would look faster in the middle than at the start and end (we're multiplying two opacity numbers) so I'm using keySplines to make the animation faster in the middle and slower at the start and end to counteract that.
<svg width="596" height="255">
<defs>
<pattern id="pattern-circles" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="6" cy="6" r="3" stroke="red" stroke-width="2" fill="none">
</circle>
</pattern>
<linearGradient id="g1" x1="100%" y1="0" x2="0" y2="0">
<stop offset="0%" stop-color="white">
<animate attributeName="stop-opacity" values="1; 0" calcMode="spline" keyTimes="0;1" keySplines="0.5 0 0.5 1" dur="2s" begin="0s" repeatCount="0" fill="freeze" />
</stop>
<stop offset="100%" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient id="g2" x1="0" y1="100%" x2="0" y2="0">
<stop offset="0%" stop-color="white" stop-opacity="1">
<animate attributeName="stop-opacity" values="1; 0" calcMode="spline" keyTimes="0;1" keySplines="0.5 0 0.5 1" dur="2s" begin="0s" repeatCount="0" fill="freeze" />
</stop>
<stop offset="100%" stop-color="white" stop-opacity="0" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#pattern-circles)" />
<rect width="100%" height="100%" fill="url(#g1)"/>
<rect width="100%" height="100%" fill="url(#g2)"/>
</svg>

SVG animate(move) text from point A to B while its content is changing

I'd like to animate(move) the text element from point A to B while its content is changing
SVG :
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="300 160 350 150" height="80px" width="100%" xml:space="preserve">
<text id="text_animated" x="422" y="280" fill="white" font-size="17">
<animate attributeName="y" from="150" to="250" begin="3s" dur="5.5s" repeatCount="1" fill="freeze"/>
</text>
<circle cx="422" cy="280" fill="red" r="5">
<animate id="animation_depth_circle" attributeName="cy" from="150" to="250" begin="indefinite" dur="1.5s" repeatCount="1" fill="freeze" onend="endAnimate()" />
</circle>
</svg>
JS:
var counter=0;
var test_t = setInterval(function(){
document.getElementById('text_animated').textContent = ""+counter;
if(counter===120){
clearInterval(test_t);
}
counter=counter+1;
},10);
My goal is to move the text (inside a circle) as the text is changing. The problem is that the text doesn't move. Only the circle does move.
btw. I can't use
document.getElementById('text_animated').setAttribuite('y',something);
because it is not synchronized with the SVG animation (if network bottleneck issues occur). I'm using Chrome.
EDIT :
I managed to move my text using dy as so :
<text x="422" y="280" fill="white" >
<animate attributeName="dy" from="0" to="250" dur="1.5s" repeatCount="indefinite"/>
</text>
The problem is that It doesn't move if I change the text with my javascript. So It's either change or move.
When you do textContent = "" + counter; you remove the animation from inside the text element. However you can declare the animation outside the animated element (the text in this case) and give an explicit target element for the animation by using the xlink:href attribute to reference the target's id: xlink:href="#text_animated".
Also you are animating the cy attribute. I prefer to use animateTransform and animate a translation instead
var counter = 0;
var test_t = setInterval(function() {
document.getElementById("text_animated").textContent = "" + counter;
if (counter === 120) {
clearInterval(test_t);
}
counter = counter + 1;
}, 10);
svg{background:black}
<svg viewBox="350 120 150 250" width="200">
<text id="text_animated" x="422" y="150" fill="white" font-size="17" transform="translate(0,0)">
</text>
<animateTransform
attributeType="XML"
attributeName="transform"
type="translate"
values="0,0; 0,100"
begin="3s"
dur="5.5s"
repeatCount="1" fill="freeze"
xlink:href="#text_animated" />
<circle cx="422" cy="280" fill="red" r="5">
<animateTransform id="animation_depth_circle"
attributeType="XML"
attributeName="transform"
type="translate"
values="0,0; 0,100"
begin="3s"
dur="1.5s"
repeatCount="1" fill="freeze"/>
</circle>
</svg>
Yet another solution would have been puting the text inside a tspan element <tspan id="text_animated"></tspan>
var counter = 0;
var test_t = setInterval(function() {
document.getElementById("text_animated").textContent = "" + counter;
if (counter === 120) {
clearInterval(test_t);
}
counter = counter + 1;
}, 10);
svg{background:black}
<svg viewBox="350 120 150 250" width="200">
<text x="422" y="150" fill="white" font-size="17" transform="translate(0,0)">
<animateTransform
attributeType="XML"
attributeName="transform"
type="translate"
values="0,0; 0,100"
begin="3s"
dur="5.5s"
repeatCount="1" fill="freeze"
/>
<tspan id="text_animated"></tspan>
</text>
<circle cx="422" cy="280" fill="red" r="5">
<animateTransform id="animation_depth_circle"
attributeType="XML"
attributeName="transform"
type="translate"
values="0,0; 0,100"
begin="3s"
dur="1.5s"
repeatCount="1" fill="freeze"/>
</circle>
</svg>
I've chanved the viewBox value because I wanted to see what I am doing. You can use what you want.

svg animationTransform from dynamic value

There is a circle. When you mouseover it increases from 1 to 2 when mouseout is reduced from 2 to 1.
With the rapid run the mouse over the visible circle of the race to widen the circle. The problem is that the animation start the circle from the values on which he managed will increase, and with the value 2. How to make so that when you mouseout the animation of reduction began with the value that had increased range.
<g transform="matrix(1 0 0 1 150 150)" id="sec7kpi">
<g transform="matrix(1 0 0 1 0 0)" id="sec7kpi-c1">
<ellipse fill="#372356" stroke="#27AE60" stroke-width="16" stroke-miterlimit="10" cx="0" cy="0" rx="71" ry="71" />
<text id="sec7text" x="-33" y="15" fill="#27AE60" font-family="LatoRegular" font-size="38.8363" pointer-events="none">KPI</text>
</g>
<defs>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
from="1"
to="2"
restart="whenNotActive"
begin="mouseover"
fill="freeze"
id="c-hover"
/>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
from="2"
to="1"
restart="whenNotActive"
begin="mouseout"
fill="freeze"
id="c-out"
/>
Simply delete the attribute from="2" from the second animateTransform element.
Because you are no longer providing a starting value for the mouseout animation, this has the effect of making this animation start at whatever value it has at the moment it is started, i.e. at the moment the mouse moves of the element. For example, if the user starts the initial mouseover animation by mousing over the element but then moves the mouse out when the scale has only reached 1.76, then the mouseout animation scaling will start at its current value, i.e. 1.76, not 2, and return to 1.
(To make the code you provided work in the code snippets below (at least in Firefox), I placed the minimal extra code required around your code to get it to work: i.e. I put <svg height="300"> at the top and </svg> at the bottom.)
Original problematic code with working snippet (essentially copied from your question):
<svg height="300">
<g transform="matrix(1 0 0 1 150 150)" id="sec7kpi">
<g transform="matrix(1 0 0 1 0 0)" id="sec7kpi-c1">
<ellipse fill="#372356" stroke="#27AE60" stroke-width="16" stroke-miterlimit="10" cx="0" cy="0" rx="71" ry="71" />
<text id="sec7text" x="-33" y="15" fill="#27AE60" font-family="LatoRegular" font-size="38.8363" pointer-events="none">KPI</text>
</g>
<defs>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
from="1"
to="2"
restart="whenNotActive"
begin="mouseover"
fill="freeze"
id="c-hover"
/>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
from="2"
to="1"
restart="whenNotActive"
begin="mouseout"
fill="freeze"
id="c-out"
/>
</svg>
Revised "fixed" code with working snippet:
<svg height="300">
<g transform="matrix(1 0 0 1 150 150)" id="sec7kpi">
<g transform="matrix(1 0 0 1 0 0)" id="sec7kpi-c1">
<ellipse fill="#372356" stroke="#27AE60" stroke-width="16" stroke-miterlimit="10" cx="0" cy="0" rx="71" ry="71" />
<text id="sec7text" x="-33" y="15" fill="#27AE60" font-family="LatoRegular" font-size="38.8363" pointer-events="none">KPI</text>
</g>
<defs>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
from="1"
to="2"
restart="whenNotActive"
begin="mouseover"
fill="freeze"
id="c-hover"
/>
<animateTransform attributeType="XML"
xlink:href="#sec7kpi-c1"
attributeName="transform"
type="scale"
dur="500ms"
to="1"
restart="whenNotActive"
begin="mouseout"
fill="freeze"
id="c-out"
/>
</svg>

Categories