Web Animation API中如何叠加transform:translate()动画并保留位移?
问题描述
我一直在原地打转(陷入困境)
我使用Web Animation API的transform:translate()实现球员绕垒跑动的动画。
runto("first").then(() => runto("second"));能执行正确的动画过程,但会清除之前到一垒的translate位移,导致最终球员位置错误。

我尝试了以下API的所有组合:
- KeyframeEffect.composite
- KeyframeEffect.iterationComposite
是否可以保留一个元素上的所有translate位移?
示例代码:
let field = document.getElementById("FIELD"); let player = field.getElementById("player"); function getCenter(el , { left, top, width, height } = el.getBoundingClientRect()) { let point = Object.assign(field.createSVGPoint(),{ x : left + width / 2, y : top + height / 2 }).matrixTransform(field.getScreenCTM().inverse()); return { cx:~~point.x, cy:~~point.y }; } function runto(destination) { let {cx:playerX,cy:playerY} = getCenter(player); let {cx:baseX ,cy:baseY } = getCenter(field.getElementById(destination)); let translateX = baseX - playerX; let translateY = baseY- playerY; let track = `<line x1="${playerX}" y1="${playerY}" x2="${baseX}" y2="${baseY}" stroke-width="10" stroke="black"/>`; field.insertAdjacentHTML("beforeend",track); return player.animate([{ //transform: `translate(${0}px,${0}px)` }, { transform: `translate(${translateX}px,${translateY}px)` }], { duration:500, fill:"forwards", composite:"add" } ).finished; // return Promise } runto("first").then(() => runto("second"));
<style> #FIELD { background: lightgreen; width: 150px; margin: 20px } </style> <svg id=FIELD viewBox="0 0 250 250" transform="rotate(45)"> <g fill="brown"> <path id="home" d="M 150 150 h 80 v 80 h -80 v -80z" fill="green"/> <path id="first" d="M 150 20 h 80 v 80 h -80 v -80z"/> <path id="second" d="M 20 20 h 80 v 80 h -80 v -80z"/> <path id="third" d="M 20 150 h 80 v 80 h -80 v -80z"/> </g> <circle id="player" cx="190" cy="190" r="30" fill="gold"/> </svg>
解决方案
问题根源
当前代码用transform: translate()实现动画时,动画结束后球员的原始cx/cy属性并未更新,第二次动画的起始帧默认基于元素的原始状态,而非动画后的位移状态,导致之前的translate被覆盖。
方式一:直接动画SVG元素位置属性(推荐)
既然是SVG元素,直接对cx和cy属性做动画,天然支持连续位置叠加,无需处理transform的复杂逻辑:
function runto(destination) { let {cx:playerX, cy:playerY} = getCenter(player); let {cx:baseX, cy:baseY} = getCenter(field.getElementById(destination)); let track = `<line x1="${playerX}" y1="${playerY}" x2="${baseX}" y2="${baseY}" stroke-width="10" stroke="black"/>`; field.insertAdjacentHTML("beforeend", track); // 动画cx和cy属性,替代transform return player.animate([ { cx: playerX, cy: playerY }, { cx: baseX, cy: baseY } ], { duration: 500, fill: "forwards" }).finished; }
方式二:累积transform并同步到元素属性
如果坚持使用transform: translate(),可在每次动画结束后,将累积位移同步到cx/cy,再重置transform,确保下一次动画起始位置正确:
function runto(destination) { let {cx:playerX, cy:playerY} = getCenter(player); let {cx:baseX, cy:baseY} = getCenter(field.getElementById(destination)); let translateX = baseX - playerX; let translateY = baseY - playerY; let track = `<line x1="${playerX}" y1="${playerY}" x2="${baseX}" y2="${baseY}" stroke-width="10" stroke="black"/>`; field.insertAdjacentHTML("beforeend", track); return player.animate([ { transform: 'none' }, { transform: `translate(${translateX}px, ${translateY}px)` } ], { duration: 500, fill: "forwards", composite: "add" }).finished.then(() => { // 将累积transform同步到cx/cy const computedTransform = window.getComputedStyle(player).transform; if (computedTransform !== 'none') { const matrix = new DOMMatrix(computedTransform); player.cx.baseVal.value += matrix.e; player.cy.baseVal.value += matrix.f; player.style.transform = 'none'; } }); }
内容的提问来源于stack exchange,提问作者Danny 'Quest' Engelman
相关产品推荐
相关产品推荐

