为什么 setInterval() 在 Firefox 中表现不同

Why does setInterval() behaves differently in Firefox

我不得不使用 vanila JS 为旋转设置动画,并遇到了 Firefox 的问题。我使用 setInterval() 为每个动画步骤设置动画,然后使用 clearInteval() 停止动画。在这个例子中,我为一个元素的旋转设置了动画。它在 Chrome 中运行良好,但在 Firefox 中不能完全完成动画,好像 Firefox 需要更长的时间来处理每个步骤。我创建了一个示例来演示这种行为。

const circle = document.getElementById('circle')
const angle = document.getElementById('angle')
const degToMove = 90 //rotate 90 degrees
const animStepLength = 10 // 10ms is one animation step
const animLength = 300 //whole animation length -> 30 animation steps -> 3deg rotation per step

const rotateCircle = () => {

  rotInterval = setInterval(()=>{

  //what rotation value currently is
    let currentVal = circle.style.transform.match(/[-+]?\d+/); 

    //add 3 deg rotation per step to it
    circle.style.transform = `rotate(${+currentVal[0] + (degToMove*animStepLength) / animLength}deg)` 

    //text output
    angle.innerHTML = `${+currentVal[0] + (degToMove*animStepLength) / animLength} deg`

  }, animStepLength)

  setTimeout(() => {

  //after all steps are done clear the interval
    clearInterval(rotInterval) 

  }, animLength);
}

circle.addEventListener('click', rotateCircle) 
body{
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  height: 100vh;
  font-family: sans-serif;
}

#circle{
  border-radius: 50%;
  background-color: skyblue;
  width: 100px;
  height: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
  <span>
    Click to rotate
  </span>
  <span id="angle">0 deg</span>
</div>

也可用 jsfiddle

而 Chrome 旋转到 90 -> 180 -> 270 -> 360 ... 在此特定示例中,Firefox 转到 57 -> 114 -> 171 -> 228 -> ...。 嗯,这基本上是 +1 rad 增加,但它必须对 animLengthanimStepLength 的 selected 值做一些事情。如果我 select 它们不同,Firefox 会显示不同的值。

简单的 CSS 动画在这里可以工作,但我有理由在这里使用 JS。

您永远无法保证 setTimeoutsetInterval 处理程序会在您要求时被调用。您应该始终将当前时间与初始时间进行比较,以确定您的动画应该显示什么样的进度,通常是通过计算动画已经过去的百分比。在下面的示例中查找 elapsedPercentage 变量。

Using setInterval is considered bad practice because of that reason. The suggested way to animate is to use nested requestAnimationFrame;

下面的脚本可以使用很多改进,但它确实向您展示了如何根据动画开始后的时长来正确更新动画。

const circle = document.getElementById('circle');
const angle = document.getElementById('angle');
const degToMove = 90; //rotate 90 degrees
 

let rotationStartTime = null;
let targetRotation = 0;
let rotationStart = 0
let animationTime = 3000;

function startRotating() {

   rotationStartTime = new Date().getTime();
   rotationStart = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
   targetRotation += degToMove;
   rotateCircle();
}

function rotateCircle() {
    const currentVal = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
    const currentTime = new Date().getTime();
    const elapsedPercentage = (currentTime - rotationStartTime) / animationTime;
    let newVal = Math.min(targetRotation, Math.round(rotationStart + (elapsedPercentage * degToMove)));
    circle.style.transform = `rotate(${newVal}deg)`; 

    //text output
    angle.innerHTML = `${newVal} deg`;
    if (newVal < targetRotation) {
        window.requestAnimationFrame(rotateCircle);
    }

}

circle.addEventListener('click', startRotating)
body{
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  height: 100vh;
  font-family: sans-serif;
}

#circle{
  border-radius: 50%;
  background-color: skyblue;
  width: 100px;
  height: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
  <span>
    Click to rotate
  </span>
  <span id="angle">0 deg</span>
</div>