You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 17:40:21