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>