Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money
The first thing that came to mind was to use setInterval. It was so easy to implement the countdown requirement and start a pleasant day of moving blocks. However, after learning about requestAnimationFrame, I found that it was much better to use it
The countdown is implemented with setInterval
Here comes the simplest code for implementing a countdown using setInterval
const totalDuration = 10 // Countdown to 10 seconds
let duration = totalDuration
let countDownInterval = null
function countDown() {
countDownInterval = setInterval(() = > {
duration = duration - 1
console.log(duration)
if (duration <= 0) {
clearInterval(countDownInterval)
}
}, 1000)
}
countDown()
Copy the code
SetInterval implementation DEMO can be clicked
Disadvantages of using the setInterval implementation
But is the countdown implemented using setInterval accurate? We can do a DEMO verification, and it can be seen that there are deviations from -2ms to 7ms per second. “Many articles on the Internet say that setInterval is set to 1000ms each time, but the actual execution is greater than 1000ms.” However, after I use it as DEMO implementation and set the 1000ms interval, In fact, it may be less than 1000ms, so using setInterval to realize the deviation per second is not a simple accumulation directly. We can calculate the difference between the end time and the start time after the countdown ends
It’s possible that the time interval isLess than 1000 ms
the
The figure below is the time difference for the countdown of 100s8ms
SetInterval deviation calculation DEMO can be clicked
const totalDuration = 100; // 100s
let duration = totalDuration;
let countDownInterval = null;
let startTime = new Date().getTime();
let endTime = startTime;
let prevEndTime = startTime
let timeDifferance = 0; // Countdown deviation per 1s, in ms
let totalTimeDifferance = 0; // Total countdown deviation, in ms
function countDown() {
countDownInterval = setInterval(() = > {
duration = duration - 1;
prevEndTime = endTime
endTime = new Date().getTime();
console.log('Current execution time:${endTime}, first execution time:${startTime}`);
console.log('Difference between current and first execution:${endTime - startTime}`)
timeDifferance = endTime - prevEndTime
document.getElementById("app").innerText = duration;
document.getElementById("differance").innerText = timeDifferance;
if (duration <= 0) {
totalTimeDifferance = endTime - startTime - totalDuration * 1000;
console.log('Cumulative time difference:${totalTimeDifferance}`)
clearInterval(countDownInterval); }},1000);
}
countDown();
Copy the code
However, if you add a thread blocking code in front of it, the variance per second can be surprisingly large
setInterval( () = > {
let n = 0;
while(n++ < 1000000000);
}, 0);
Copy the code
After adding this thread blocking code, the countdown is only 10 seconds, and the cumulative time difference is up to 8s
In a word, we should try to correct the error value. The general idea is that the next time the countdown is executed, the execution deviation value is advanced, so the time interval of each execution is not fixed at 1s, so setTimeout is used instead of setInterval
Use setTimeout to implement the countdown
According to the above, in order to correct the calculation error of the countdown, we can modify the above code to
const totalDuration = 100; // 100s
let duration = totalDuration;
let countDownInterval = null;
let startTime = new Date().getTime();
let endTime = startTime;
let prevEndTime = startTime;
let timeDifferance = 0; // Countdown deviation per 1s, in ms
let totalTimeDifferance = 0; // Total countdown deviation, in ms
let interval = 1000; // 1s
function countDown() {
duration = duration - 1;
endTime = new Date().getTime();
timeDifferance = endTime - prevEndTime;
console.log('Current countdown:${duration}, deviation value of execution per second:${timeDifferance}`)
let nextTime = interval - timeDifferance
// If the next execution takes longer than the current cycle, special processing is required
if (nextTime < 0) {
nextTime = 0
}
document.getElementById("nextTime").innerText = nextTime;
if (duration <= 0) {
totalTimeDifferance = endTime - startTime - totalDuration * 1000;
console.log('Cumulative execution deviation value:${totalTimeDifferance}`)
clearTimeout(countDownInterval);
} else {
countDownInterval = setTimeout(() = > countDown(), nextTime);
}
}
countDownInterval = setTimeout(() = > countDown(), interval);
Copy the code
Note: If the next execution interval is less than 0 seconds, you need to do something special
As shown in the figure below, the cumulative time difference can be controlled within 1s
Also add thread blocking code countdown 10s, error value can be reduced to 2.5s, indicating that the optimization effect is very obvious, of course, the actual process is so serious blocking is very extreme, the focus is no longer to reduce the delay, but to focus on the reconstruction of the blocking code
SetTimeout implementation countdown implementation DEMO can be clicked
Is there a better solution than using setTimeout to implement the countdown?
What is requestAnimationFrame
RAF mainly redraws the page according to the refresh frequency of the monitor (60Hz or 75Hz), and synchronously redraws the page according to this refresh frequency, that is, the maximum multiple drawing frequency of 60 or 75 times is about 1s. Calculated according to 60Hz, each redrawing is about 16.67ms. If setInterval is set to a frequency lower than 16.67ms, it will cause transition drawing problems. If setInterval is set to a frequency higher than 16.67ms, it may drop frames
We want to know is that the window. RequestAnimationFrame (the callback) before the next redrawing execution time is in the browser calls the callback function to obtain the latest animation in the RAF calculation results
SetTimeout versus requestAnimationFrame
RequestAnimationFrame will stop executing when the TAB in modern browsers is not activated
The current TAB page is not in the “active” state, setTimeout will continue to perform tasks in the background, we can add log to the setTimeout DEMO above, when we switch to another TAB, the current TAB page is out of focus, setTimeout will continue to execute. However, using requestAnimationFrame (RAF), you can see the log console that the time period we switch out is not executed, which can improve performance
Of course setTimeout can be optimized by listening for the window.visibilityChange() event
SetTimeout, setInterval belongs to JS engine, RAF belongs to GUI engine
When I was working on the project, I found that if I was loading a large graph, the countdown made by setInterval would stall and suddenly speed up. The reason is that the JS thread and GUI thread in JS are mutually exclusive. If GUI thread is executed for a long time, the JS thread will be blocked. We’ll do a DEMO to verify that
Use requestAnimationFrame instead of setInterval to implement the countdown scheme
const totalDuration = 10 * 1000;
let requestRef = null;
let startTime;
let prevEndTime;
let prevTime;
let currentCount = totalDuration;
let endTime;
let timeDifferance = 0; // Countdown deviation per 1s, in ms
let interval = 1000;
let nextTime = interval;
setInterval(() = > {
let n = 0;
while (n++ < 1000000000);
}, 0);
const animate = (timestamp) = > {
if(prevTime ! = =undefined) {
const deltaTime = timestamp - prevTime;
if (deltaTime >= nextTime) {
prevTime = timestamp;
prevEndTime = endTime;
endTime = new Date().getTime();
currentCount = currentCount - 1000;
console.log("currentCount: ", currentCount / 1000);
timeDifferance = endTime - startTime - (totalDuration - currentCount);
console.log(timeDifferance);
nextTime = interval - timeDifferance;
// If it is too slow, execute the next loop immediately
if (nextTime < 0) {
nextTime = 0;
}
console.log('The time to perform the next rendering is:${nextTime}ms`);
if (currentCount <= 0) {
currentCount = 0;
cancelAnimationFrame(requestRef);
console.log('Cumulative deviation value:${endTime - startTime - totalDuration}ms`);
return; }}}else {
startTime = new Date().getTime();
prevTime = timestamp;
endTime = new Date().getTime();
}
requestRef = requestAnimationFrame(animate);
};
requestRef = requestAnimationFrame(animate);
Copy the code
RequestAnimationFrame implementation DEMO is clickable
Even when executing thread-blocking code, the deviation is only 98ms
Special attention points
Compatibility issues
RequestAnimationFrame is compatible with IE10 and above, so don’t worry too much about compatibility
Destroy the listening event when you leave the page
-
The RAF is destroyed using cancelAnimationFrame
-
Use webkitCancelRequestAnimationFrame superior to use webkitCancelAnimationFrame wekit kernel browsers
Implement the countdown case using React Hooks and requestAnimationFrame
If you want to deal with the deviation value, you can refer to the above written processing oh
import React, { useState, useEffect, useRef } from "react";
const [count, setCount] = useState<number>(0)
const [duration, setTotalDuration] = useState<number>(0)
const requestRef = useRef(null);
const previousTimeRef = useRef(null);
const currentCountRef = useRef<number>(0);
const animate = time= > {
if(previousTimeRef.current ! = =undefined) {
const deltaTime = time - previousTimeRef.current;
if (deltaTime > 1000) {
if (currentCountRef.current > 0) {
previousTimeRef.current = time;
setCount(prevCount= > {
currentCountRef.current = prevCount - 1000
return prevCount - 1000
});
} else {
setCount(0)
cancelAnimationFrame(requestRef.current);
return}}}else {
previousTimeRef.current = time;
}
requestRef.current = requestAnimationFrame(animate);
}
useEffect(() = > {
const totalDuration = 60 * 1000
setCount(totalDuration)
setTotalDuration(totalDuration)
}, [])
useEffect(() = > {
if (duration <= 0) {
return
}
currentCountRef.current = duration
previousTimeRef.current = undefined
if (requestRef.current) {
cancelAnimationFrame(requestRef.current)
}
requestRef.current = requestAnimationFrame(animate);
return () = > cancelAnimationFrame(requestRef.current)
}, [duration])
Copy the code
The last
These are some of the things I’ve learned using requestAnimationFrame to implement the countdown. I hope you’ll find it helpful!
More articles: “No more confusion! This article will give you a silky experience with Vue3.0 development”
“Welcome to the discussion in the comments section. The excavation authorities will draw 100 nuggets in the comments section after project Diggnation. See the event article for details.”