动态添加的SVG动画有时无法触发,Chrome下问题排查求助
Chrome中SVG动画延迟触发问题分析与解决
问题描述
每次鼠标悬停灰色SVG方块(元素#test)时,会创建一个圆形并添加从(0,0)到(300,300)的translate动画,但在最新版Chrome里,有时圆形已经创建出来,动画却要等到下一次事件触发才会开始渲染,这是怎么回事?
代码示例
HTML
<svg id="test" />
CSS
#test { background-color: lightgray; height: 300px; width: 300px; } .ball { fill: black; r: 10px; }
JavaScript
function anim() { const circ = document.createElementNS("http://www.w3.org/2000/svg", "circle"); circ.setAttribute("class", "ball"); circ.setAttribute("cx", 0); circ.setAttribute("cy", 0); const anim = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); anim.setAttribute("attributeName", "transform"); anim.setAttribute("type", "translate"); anim.setAttribute("begin", "indefinite"); anim.setAttribute("from", "0,0"); anim.setAttribute("to", "300,300"); anim.setAttribute("dur", "2s"); anim.setAttribute("repeatCount", "1"); anim.addEventListener("endEvent", (e) => { e.target.parentElement.remove(); }); circ.append(anim); test.append(circ); anim.beginElement(); } const test = document.querySelector("#test"); test.addEventListener("mouseenter", anim);
问题原因
核心是Chrome的渲染队列与事件循环的时序冲突:
- 在
mouseenter的同步回调里,刚把新元素插入DOM,浏览器还没完成该元素的布局计算、样式解析等渲染前置工作 - 此时直接调用
anim.beginElement(),SVG动画模块无法找到已就绪的目标元素,导致动画指令没有被正确调度 - 等到下一次事件触发时,之前的元素已经完成渲染准备,动画才会生效
解决方案
把动画触发操作推迟到浏览器完成当前DOM更新后的下一个渲染周期,用requestAnimationFrame包裹beginElement()调用即可:
修改后的JavaScript代码:
function anim() { const circ = document.createElementNS("http://www.w3.org/2000/svg", "circle"); circ.setAttribute("class", "ball"); circ.setAttribute("cx", 0); circ.setAttribute("cy", 0); const anim = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); anim.setAttribute("attributeName", "transform"); anim.setAttribute("type", "translate"); anim.setAttribute("begin", "indefinite"); anim.setAttribute("from", "0,0"); anim.setAttribute("to", "300,300"); anim.setAttribute("dur", "2s"); anim.setAttribute("repeatCount", "1"); anim.addEventListener("endEvent", (e) => { e.target.parentElement.remove(); }); circ.append(anim); test.append(circ); // 推迟到下一个渲染周期触发动画,确保元素已准备就绪 requestAnimationFrame(() => { anim.beginElement(); }); } const test = document.querySelector("#test"); test.addEventListener("mouseenter", anim);
补充说明
requestAnimationFrame是最优选择,因为它会在浏览器下一次重绘前执行回调,完美贴合渲染时序- 也可以用
setTimeout(() => {}, 0)替代,但requestAnimationFrame更精准,不会引入不必要的延迟 - 这种问题是Chrome对SVG动画的调度逻辑导致的,Firefox等其他浏览器可能不会出现该现象
内容的提问来源于stack exchange,提问作者ketchupcoke
相关产品推荐
相关产品推荐

