Home > Enterprise >  Why does setInterval() behaves differently in Firefox
Why does setInterval() behaves differently in Firefox

Time:03-09

I had to animate the rotation using vanila JS and encountered the problem with Firefox. I used setInterval() to animate each animation step and then clearInteval() to stop the animation. In this example I animated the rotation of an element. It works fine in Chrome, but doesn't quite finish the animation in Firefox, as if Firefox takes longer to process each step. I created an example, demonstrating this behaviour.

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>

Also available as jsfiddle

While Chrome rotates to 90 -> 180 -> 270 -> 360 ... Firefox goes to 57 -> 114 -> 171 -> 228 -> ... in this particular example. Well, this is basically 1 rad increase, but it has to do something with the selected values for animLength and animStepLength. If I select them differently, Firefox shows different values.

The simple CSS animation would work here but there are reasons for me to use JS here.

CodePudding user response:

You can never guarantee that a setTimeout or setInterval handler will be called when you ask it to. You should always compare the current time against the initial time to figure out what kind of progress your animation should show, usually by figuring out what percentage of the animation has elapsed. Look for the elapsedPercentage variable in the example below.

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

The script below can use lots of improvements but it does show you how to properly update your animation based on how long it's been since the animation started.

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>

  • Related