使用 requestAnimationFrame() 重启时如何保持箭头位置不变?
How to keep the arrow position unchanged when restart it using requestAnimationFrame()?
我试图理解 MDN 上的 example。
current code的效果让我有点迷惑——暂停后如果稍等片刻再开始,箭头的位置不是原来停的地方。
如何确保从中断处重新开始?我想到了下面的方法,但是我不是很满意。
const spinner = document.querySelector('div')
let rotateCount = 0
let startTime = null
let rAF
let spinning = false
let previousRotateCount = 0
let hasStopped = false
let rotateInterval
function draw(timestamp) {
if (hasStopped) {
// The angle of each rotation is constant
rotateCount += rotateInterval
rotateCount %= 360
} else {
if (!startTime) {
startTime = timestamp
}
rotateCount = (timestamp - startTime) / 3
rotateCount %= 360
rotateInterval = rotateCount - previousRotateCount
previousRotateCount = rotateCount
}
console.log(rotateCount)
spinner.style.transform = `rotate(${rotateCount}deg)`
rAF = requestAnimationFrame(draw)
}
document.body.addEventListener('click', () => {
if (spinning) {
hasStopped = true
cancelAnimationFrame(rAF)
} else {
draw()
}
spinning = !spinning
})
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: #f00;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
}
<div>↻</div>
希望有更好的方法。非常感谢。
这个示例代码有点奇怪,他们定义了一个从未实际使用过的全局 rotateCount
并且他们对时间戳的使用是......是的,令人困惑。
他们的 let rotateCount = (timestamp - startTime) / 3;
使得动画将花费 1080 毫秒来执行完整的旋转(360 度 x 3 = 1080 毫秒)。
但这是基于当前时间和开始时间的差异。所以确实,即使你暂停动画,当重新启动它时,它就像从未暂停过一样。
为此,您需要实际使用全局变量 rotateCount
(不在 draw
中重新定义新变量),并每次按旋转量递增它自上次开奖以来需要,而不是从整体开始。
那么您只需要确保在恢复动画时更新最后绘制的时间戳,并让动画真正暂停和恢复。
const spinner = document.querySelector('div');
const duration = 1080; // ms to perform a full revolution
const speed = 360 / duration;
let rotateCount = 0;
let rAF;
// we'll set this in the starting code
// (in the click event listener)
let lastTime;
let spinning = false;
// Create a draw() function
function draw(timestamp) {
// get the elapsed time since the last time we did fire
// we directly get the remainder from "duration"
// because we're in a looping animation
const elapsed = (timestamp - lastTime) % duration;
// for next time, lastTime is now
lastTime = timestamp;
// add to the previous rotation how much we did rotate
rotateCount += elapsed * speed;
// Set the rotation of the div to be equal to rotateCount degrees
spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
// Call the next frame in the animation
rAF = requestAnimationFrame(draw);
}
// event listener to start and stop spinner when page is clicked
document.body.addEventListener('click', () => {
if(spinning) {
cancelAnimationFrame(rAF);
spinning = false;
} else {
// reset lastTime with either the current frame time
// or JS time if unavailable
// so that in the next draw
// we see only the time that did elapse
// from now to then
lastTime = document.timeline?.currentTime || performance.now();
// schedule the next draw
requestAnimationFrame( draw )
spinning = true;
}
});
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: red;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
user-select: none;
}
<div>↻</div>
我试图理解 MDN 上的 example。
current code的效果让我有点迷惑——暂停后如果稍等片刻再开始,箭头的位置不是原来停的地方。
如何确保从中断处重新开始?我想到了下面的方法,但是我不是很满意。
const spinner = document.querySelector('div')
let rotateCount = 0
let startTime = null
let rAF
let spinning = false
let previousRotateCount = 0
let hasStopped = false
let rotateInterval
function draw(timestamp) {
if (hasStopped) {
// The angle of each rotation is constant
rotateCount += rotateInterval
rotateCount %= 360
} else {
if (!startTime) {
startTime = timestamp
}
rotateCount = (timestamp - startTime) / 3
rotateCount %= 360
rotateInterval = rotateCount - previousRotateCount
previousRotateCount = rotateCount
}
console.log(rotateCount)
spinner.style.transform = `rotate(${rotateCount}deg)`
rAF = requestAnimationFrame(draw)
}
document.body.addEventListener('click', () => {
if (spinning) {
hasStopped = true
cancelAnimationFrame(rAF)
} else {
draw()
}
spinning = !spinning
})
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: #f00;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
}
<div>↻</div>
希望有更好的方法。非常感谢。
这个示例代码有点奇怪,他们定义了一个从未实际使用过的全局 rotateCount
并且他们对时间戳的使用是......是的,令人困惑。
他们的 let rotateCount = (timestamp - startTime) / 3;
使得动画将花费 1080 毫秒来执行完整的旋转(360 度 x 3 = 1080 毫秒)。
但这是基于当前时间和开始时间的差异。所以确实,即使你暂停动画,当重新启动它时,它就像从未暂停过一样。
为此,您需要实际使用全局变量 rotateCount
(不在 draw
中重新定义新变量),并每次按旋转量递增它自上次开奖以来需要,而不是从整体开始。
那么您只需要确保在恢复动画时更新最后绘制的时间戳,并让动画真正暂停和恢复。
const spinner = document.querySelector('div');
const duration = 1080; // ms to perform a full revolution
const speed = 360 / duration;
let rotateCount = 0;
let rAF;
// we'll set this in the starting code
// (in the click event listener)
let lastTime;
let spinning = false;
// Create a draw() function
function draw(timestamp) {
// get the elapsed time since the last time we did fire
// we directly get the remainder from "duration"
// because we're in a looping animation
const elapsed = (timestamp - lastTime) % duration;
// for next time, lastTime is now
lastTime = timestamp;
// add to the previous rotation how much we did rotate
rotateCount += elapsed * speed;
// Set the rotation of the div to be equal to rotateCount degrees
spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
// Call the next frame in the animation
rAF = requestAnimationFrame(draw);
}
// event listener to start and stop spinner when page is clicked
document.body.addEventListener('click', () => {
if(spinning) {
cancelAnimationFrame(rAF);
spinning = false;
} else {
// reset lastTime with either the current frame time
// or JS time if unavailable
// so that in the next draw
// we see only the time that did elapse
// from now to then
lastTime = document.timeline?.currentTime || performance.now();
// schedule the next draw
requestAnimationFrame( draw )
spinning = true;
}
});
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: red;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
user-select: none;
}
<div>↻</div>