如何取消当前动画并立即使用鼠标事件和 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 显示器上甚至更快。为避免这种情况,请使用增量时间。
我希望点跟随鼠标光标,例如点击。 代码看似简单,但每点击一次,圆点就会跑得更短,不会到达目标。
问题是为什么?
代码在这里:
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 显示器上甚至更快。为避免这种情况,请使用增量时间。