如何使用纯JavaScript实现setAttribute修改SVG路径的平滑过渡效果
SVG路径d属性平滑过渡的纯JS实现方案
要实现d属性的平滑过渡,必须保证起始路径和目标路径的SVG路径命令类型、数量完全一致,仅坐标数值不同,否则无法进行线性插值,会出现异常变形。
方案1:使用Web Animations API(推荐)
这是浏览器原生支持的动画接口,无需引入第三方库,代码简洁,效果和SMIL完全一致,现代浏览器均支持。
你的SVG可以简化为不带SMIL标签的版本:
<svg viewBox="0 0 600 600"> <path id="foo" d="M10 10 C 20 20, 40 20, 50 10" stroke="black" fill="black"/> </svg>
对应的JS逻辑:
const path = document.getElementById('foo'); // 定义起始和目标路径d值 const startD = 'M10 10 C 20 20, 40 20, 50 10'; const endD = 'M500 500 C 20 20, 40 20, 50 10'; path.addEventListener('click', () => { path.animate( // 关键帧 [ { d: startD }, { d: endD } ], // 动画配置 { duration: 5000, // 动画时长5秒,对应SMIL的dur="5s" iterations: Infinity, // 无限循环,对应repeatCount="indefinite",单次播放改为1即可 fill: 'forwards' // 动画结束后保持最后一帧状态 } ); });
方案2:手动实现帧插值(兼容老旧浏览器)
如果需要兼容不支持Web Animations API的老旧环境,可以自行解析路径数值,通过requestAnimationFrame逐帧插值更新d属性。
完整代码示例:
const path = document.getElementById('foo'); const startD = 'M10 10 C 20 20, 40 20, 50 10'; const endD = 'M500 500 C 20 20, 40 20, 50 10'; const duration = 5000; // 动画时长5秒 // 提取路径字符串中所有数值的工具函数 function getPathNumbers(d) { return d.match(/[-+]?\d*\.?\d+/g).map(Number); } // 把插值后的数值替换回路径模板,生成当前帧d值 function buildPath(template, numbers) { let numIndex = 0; return template.replace(/[-+]?\d*\.?\d+/g, () => numbers[numIndex++].toFixed(2)); } // 预解析路径的命令结构和数值 const startNumbers = getPathNumbers(startD); const endNumbers = getPathNumbers(endD); // 生成保留命令结构、数值替换为占位符的模板 const pathTemplate = startD.replace(/[-+]?\d*\.?\d+/g, '{}'); path.addEventListener('click', () => { const startTime = performance.now(); function animateFrame(currentTime) { const elapsed = currentTime - startTime; let progress = Math.min(elapsed / duration, 1); // 如需调整动画缓动效果,可在这里修改progress的计算逻辑,替换为对应缓动函数即可 // 线性插值计算当前帧的所有路径数值 const currentNumbers = startNumbers.map((startVal, index) => { return startVal + (endNumbers[index] - startVal) * progress; }); // 更新路径属性 path.setAttribute('d', buildPath(pathTemplate, currentNumbers)); // 动画未结束则继续请求下一帧 if (progress < 1) { requestAnimationFrame(animateFrame); } // 如需无限循环,把上面的if判断替换为以下两行即可 // if (progress >= 1) startTime = currentTime; // requestAnimationFrame(animateFrame); } requestAnimationFrame(animateFrame); });
内容的提问来源于stack exchange,提问作者sonia maklouf
相关产品推荐
相关产品推荐

