如何取消当前动画并立即使用鼠标事件和 requestAnimationFrame() 启动新动画

How to cancel current animation and immediately start new one with mouse event and requestAnimationFrame()

我希望点跟随鼠标光标,例如点击。 代码看似简单,但每点击一次,圆点就会跑得更短,不会到达目标。

问题是为什么?

代码在这里:

https://jsfiddle.net/thiefunny/ny0chx3q/3/

HTML

<circle r="10" cx="300" cy="300" />

JavaScript

const circle = document.querySelector("circle")

window.addEventListener("click", mouse => {

    const animation = _ => {

        let getCx = Number(circle.getAttribute('cx'))
        let getCy = Number(circle.getAttribute('cy'))

        circle.setAttribute("cx", `${getCx + (mouse.clientX - getCx)/10}`);
        circle.setAttribute("cy", `${getCy + (mouse.clientY - getCy)/10}`);

        requestAnimationFrame(animation)

    }

    requestAnimationFrame(animation)

});

编辑:对于这个任务,我需要 requestAnimationFrame(),而不是 CSS,因为这只是最简单的例子,但我想在以后为运动添加更多的复杂性,包括多个点、随机参数等等,就像我在这里所做的那样:https://walanus.pl

我花了很多时间试验,但我唯一的结论是,在点击事件之后我应该以某种方式取消当前动画并开始新的动画,以便为下一个动画重新开始。

你不需要 requestAnimationFrame:

const circle = document.querySelector("circle")

window.addEventListener("click", e => {

  let targetCircleX = e.clientX;
  let targetCircleY = e.clientY;
  let getCx = Number(circle.getAttribute('cx'))
  let getCy = Number(circle.getAttribute('cy'))
  let cx = targetCircleX - getCx;
  let cy = targetCircleY - getCy;
  
  circle.style.transform = `translate3d(${cx}px, ${cy}px, 0)`;
  
});
circle {
  transition-duration: .2s;
}
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">
  <circle r="10" cx="100" cy="100" />
</svg>
    

编辑:CSS animations 是一种简单而强大的方法来为网络中的事物制作动画,但是手动控制动画,正确完成,总是需要更多的工作,即性能循环,适当的时间等(通过顺便说一句,提到的网站不会理会这些)。因此,为了完整回答,下面是 requestAnimationFrame 的变体

const circle = document.querySelector("circle");

const fps = 60;
const delay = 1000 / fps;
let rafId;

window.addEventListener("click", e => {

    cancelAnimationFrame(rafId);

    let [time, cx, cy, xf, yf] = [0];
    let r = +circle.getAttribute('r');
    let [X, x] = [e.clientX - r, +circle.getAttribute('cx')];
    let [Y, y] = [e.clientY - r, +circle.getAttribute('cy')];
    const decel = 10;
    
    const anim = now => {
        const delta = now - time;
        if (delta > delay) {
            time = now - (delta % delay);
            [x, y] = [x + (X - x) / decel, y + (Y - y) / decel];
            [xf, yf] = [x.toFixed(1), y.toFixed(1)];
            if (cx === xf && cy === yf)
                return;
            circle.setAttribute("cx", cx = xf);
            circle.setAttribute("cy", cy = yf);
        }
        rafId = requestAnimationFrame(anim);
    }

    anim(time);
});
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500" style="background: black">
  <circle r="10" cx="100" cy="100" fill="red"/>
</svg>

The question is why

嗯,您似乎知道原因:您永远不会停止动画循环,因此在每一帧它都会尝试转到动画循环开始时的 mouse.clientN 位置。然后在下一次单击时,第二个动画循环将开始,运行与第一个动画循环平行,并且两者将相互竞争以走向自己的 mouse.clientN 位置。


要避免这种情况,正如您所确定的,您可以使用 cancelAnimationFrame 简单地停止上一个循环。它所需要的只是一个动画范围和点击处理程序都可以访问的变量。
然而,让你的动画循环继续下去只是在杀死树木。因此,在从 animation.

内部再次调用 requestAnimationFrame 之前,让您的代码检查它是否已到达目标位置

const circle = document.querySelector("circle")
{

  let anim_id; // to be able to cancel the animation loop
  window.addEventListener("click", mouse => {
    const animation = _ => {

      const getCx = Number(circle.getAttribute('cx'))
      const getCy = Number(circle.getAttribute('cy'))
      const setCx = getCx + (mouse.clientX - getCx)/10;
      const setCy = getCy + (mouse.clientY - getCy)/10;

      circle.setAttribute("cx", setCx);
      circle.setAttribute("cy", setCy);
      // only if we didn't reach the target
      if(
        Math.floor( setCx ) !== mouse.x && 
        Math.floor( setCy ) !== mouse.y
      ) {
        // continue this loop
        anim_id = requestAnimationFrame(animation);
      }
    }
    // clear any previous animation loop
    cancelAnimationFrame( anim_id );
    anim_id = requestAnimationFrame(animation)
  });
  
}
svg { border: 1px solid }
<svg viewBox="0 0 500 500" width="500" height="500">
  <circle r="10" cx="100" cy="100" />
</svg>

此外,请注意您的动画在配备 120Hz 显示器的设备上 运行 比配备 60Hz 显示器的设备快两倍,在 240Hz 显示器上甚至更快。为避免这种情况,请使用增量时间。