I’m trying to animate motions of a circle moving along edges (SVG paths) in a graph.
It looks like I don’t understand the dur
attribute of motions: even if I create and append a second motion after the first one ended, with its duration set to 2 seconds, then nothing happens. If I set its duration to 4 seconds instead, then the animation starts in the middle of the path, as if 2 seconds had already elapsed.
It feels like dur
is a duration since the page was loaded?
<svg width="300" height="200">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#f00" />
</marker>
</defs>
<circle cx="50" cy="50" r="20" stroke="black" stroke-width="3" fill="red" />
<circle cx="200" cy="100" r="20" stroke="black" stroke-width="3" fill="red" />
<circle cx="200" cy="175" r="20" stroke="black" stroke-width="3" fill="red" />
<circle cx="50" cy="175" r="20" stroke="black" stroke-width="3" fill="red" />
<path id="edge1" d="M70,50 Q125,0 180,90" stroke="black" stroke-width="2" fill="none" marker-end="url(#arrow)" />
<path id="edge2" d="M70,175 Q125,125 180,100" stroke="black" stroke-width="2" fill="none" marker-end="url(#arrow)" />
<circle id="animCircle" r="10" fill="blue">
<animateMotion repeatCount="1">
<mpath href="#edge1" />
</animateMotion>
</circle>
</svg>
<script>
document.addEventListener("DOMContentLoaded", function() {
const animCircle = document.getElementById('animCircle');
// Function to create animateMotion elements dynamically
function createAnimateMotion(pathId, dur) {
const animElement = document.createElementNS('http://www.w3.org/2000/svg', 'animateMotion');
animElement.setAttribute('dur', dur);
animElement.setAttribute('repeatCount', '1');
const mpath = document.createElementNS('http://www.w3.org/2000/svg', 'mpath');
mpath.setAttribute('href', pathId);
animElement.appendChild(mpath);
return animElement;
}
// Initially set the animation along the first edge
const firstAnim = createAnimateMotion('#edge1', '2s');
animCircle.appendChild(firstAnim);
firstAnim.addEventListener('endEvent', function() {
// Remove the first animation
animCircle.removeChild(firstAnim);
// Set the animation along the second edge
const secondAnim = createAnimateMotion('#edge2', '4s');
animCircle.appendChild(secondAnim);
});
});
</script>
How do I create my animateMotion
elements so that they play one after the other, without depending on the time already elapsed?
2
Answers
For now I'm computing a time delta to use as value for
begin
:This should ensure that an animation starts almost exactly when it is created with
createAnimateMotion
.You can queue up all animations inside the
<animateMotion>
element, and remove them one by one using therepeat
eventThis approach requires you to set the
repeatCount
to the number of animations you want to play, and won’t work forindefinite
(infinitely looping) animations.You can also inline this function if this is all you need
Here’s a hacky CSS solution with different trade-offs: