如何让SVG animateMotion动画依次独立播放,不受页面加载时长影响?
SVG animateMotion 动态添加时依次播放不受文档时间线影响的解决方案
问题描述
我尝试实现圆形沿SVG路径依次运动的动画,但遇到了animateMotion的dur属性问题:第一个动画结束后动态创建并添加第二个动画时,若设置时长为2秒则完全没反应;设为4秒时,动画会从路径中间开始,仿佛它的时长是从页面加载时就开始计算的。
问题代码
<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>
问题根源
SVG动画默认使用文档时间线(从页面加载时开始计时),动态创建的animateMotion如果不指定begin属性,默认值是0,也就是页面加载瞬间开始。当第一个动画2秒结束后再添加第二个动画,此时文档时间已经过了2秒:
- 若第二个动画
dur设为2秒,它的时间窗口是0-2s,已经完全过去,所以不会播放; - 若
dur设为4秒,时间窗口是0-4s,当前时间处于窗口中间位置,所以动画从路径50%的位置开始。
解决方案
给动态创建的animateMotion设置begin="indefinite",让它不会自动触发,然后在添加到DOM后调用beginElement()方法手动启动动画,这样动画会以当前时间为起点,完整播放整个时长。
修改后的代码
<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'); // 设置begin为indefinite,禁止自动触发 animElement.setAttribute('begin', 'indefinite'); 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.beginElement(); 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); // 手动启动第二个动画 secondAnim.beginElement(); }); }); </script>
关键修改点
- 在
createAnimateMotion函数中添加animElement.setAttribute('begin', 'indefinite');,让动画不会自动跟随文档时间线启动; - 每次将动画添加到DOM后,调用
beginElement()方法手动触发动画,确保动画从当前时间开始完整播放。
内容的提问来源于stack exchange,提问作者pawamoy
相关产品推荐
相关产品推荐

