首页 > 解决方案 > 使用 requestAnimationFrame() 重新启动时如何保持箭头位置不变?

问题描述

我试图理解 MDN 上的一个例子

当前代码的效果对我来说有点令人困惑-如果您稍等片刻然后重新开始,则暂停后箭头的位置不是它最初停止的位置。

如何确保从我离开的地方重新开始?我想了一个方法如下,但我不是很满意。

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>↻&lt;/div>

我希望有更好的方法。非常感谢你。

标签: javascriptrequestanimationframe

解决方案


这个示例代码有点奇怪,他们定义了一个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>↻&lt;/div>


推荐阅读