requestAnimationFrame
和 FPS
Most monitors have a refresh rate of 60 times per second, which means the picture feels smooth to the human eye at 60 FPS. The page of the browser is also rendered frame by frame, so to ensure smooth page, the time interval of each frame rendering should not exceed 1000/60 ≈ 16.7ms, if three consecutive frames are less than 24FPS, it is considered that there is a lag. Of course when I play LOL I feel like it’s a lot worse than 50fps. That’s why I bought the rainbow 3070
RequestAnimationFrameMDN explanation:
Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation. This method takes as an argument a callback function that is executed before the browser’s next redraw. This will cause the browser to call the animation function you passed into the method (that is, your callback) before the next redraw. Callbacks are typically executed 60 times per second. Raf will centralize dom changes to avoid repeated drawing.
Raf registered callbacks are executed before each browser redraw, usually with a number that matches the browser screen refresh, meaning my 60fps monitor is refreshed every 16.67ms, so we can calculate the current FPS in the callback.
// If three consecutive draws are below 24 frames/SEC, it is considered to be stuck
const smoothThreshold = 1000 / 24;
function calcFps() {
let now = performance.now();
let frame = 0;
let fps = 0;
function checkFps() {
frame++;
if (frame >= 3) {
console.log("The page has dropped frames.");
}
// If the next render frame exceeds the fluency threshold, it is considered to be stuck
if (performance.now() - now < smoothThreshold) {
fps = (frame * 1000) / (performance.now() - now);
frame = 0;
}
now = performance.now();
log(fps);
window.requestAnimationFrame(checkFps);
}
requestAnimationFrame(checkFps);
}
window.requestAnimationFrame(calcFps);
// Throttle controls the print frequency
function log (fps) {
return throttle(() = > console.log(fps));
}
function throttle(fn, timeout = 600) {
let timer = null;
let now = performance.now();
return (. args) = > {
clearTimeout(timer);
timer = setTimeout(() = > {
if (performance.now() - now >= timeout) {
fn.call(null. args); now = performance.now(); }},Math.max(timeout - (performance.now() - now)));
};
}
Copy the code
Why not usesetTimeout
Calculate the FPS?
There are two reasons:
-
Because setTimeout is affected by the event queue, the setTimeout callback is not pushed until the task stack is empty, which results in the setTimeout callback being executed later than the set interval. RequestAnimationFrame callback is scheduled by the browser and executed automatically before the frame is drawn. There is no accuracy problem.
-
The screen drawing frequency of different devices may be different. For example, the screen drawing time of 120hz is 8.4ms. SetTimeout can only set a fixed interval, which may not be the same as the screen refresh time.
userequestAnimationFrame
andsetTimeout
Do the animation
The way to achieve animation can be CSS or JS, first discuss a performance of JS animation. RequestAnimationFrame and setTimeout are compared for animation. Below, two small balls are used for uniform motion. SetTimeout is used for no. 1 ball and RAF is used for No. 2 ball.
<! DOCTYPEhtml>
<html lang="en">
<head>
<title>Document</title>
<style>
.span2..span4 {
color: white;
text-align: center;
line-height: 30px;
height: 30px;
width: 30px;
background-color: red;
border-radius: 50%;
margin-bottom: 50px;
}
</style>
</head>
<body>
<div class="span2">1</div>
<div class="span4">2</div>
<script>
// Set it to 60 frames
const threshold = 1000 / 60;
const duration = 5000;
// Move forward each frame
const step = 1000 / (duration / threshold);
const span2 = document.querySelector(".span2");
const span4 = document.querySelector(".span4");
let n = performance.now();
function moveSetTimeout(el, count = 0) {
setTimeout(() = > {
count++;
// Tests whether the callback executes later than the set interval
if (performance.now() - n > 20) {
console.log("setimeout callback trigger:", performance.now() - n);
}
n = performance.now();
el.style.transform = `translateX(${count * step}px)`;
if (count * step < 1000) {
moveSetTimeout(el, count);
}
}, threshold);
}
function moveRaf(el, count = 0) {
const run = () = > {
count++;
el.style.transform = `translateX(${count * step}px)`;
if (count * step < 1000) {
window.requestAnimationFrame(run); }};window.requestAnimationFrame(run);
}
moveSetTimeout(span2);
window.requestAnimationFrame(() = > moveRaf(span4));
</script>
</body>
</html>
Copy the code
Motion giFs:
It can be seen that raf2 ball moves smoothly, while No. 1 ball shakes and loses frames. In addition, the displacement distance of No. 1 ball is a little behind that of No. 2 ball, and there is no overlap in position. The console prints multiple times. Let’s analyze why
Cause analysis,
- Why is the timing of timer callback incorrect
- Why is there jitter
Why is the timing of timer callback incorrect
Due to the influence of event loop, even if the interval of 16.67ms is set, the timer callback is a macro task, and the macro task will not be pushed before the synchronization task stack is cleared. When the main thread executes the synchronization task for a long time, the timer callback execution time will also be delayed, resulting in the interval of callback execution greater than 16.67ms
Why is there jitter
Because the DOM changes to the setTimeout operation are executed before the browser’s next redraw, otherwise they just stay in memory. Because the timer callback cannot guarantee the overlap with the browser redraw time, it will lead to a frame is not drawn, directly draw the next frame, there is a jump, so it will jitter. The following example moves the DOM 3.3px to the left per frame;
const threshold = 1000 / 60;
const duration = 5000;
// Move forward each frame
const step = 1000 / (duration / threshold); / / 3.33 px
Copy the code
// The actual interval between each timer callback being triggered
setimeout callback call: 17.79
setimeout callback call: 21.60
setimeout callback call: 16.79
setimeout callback call: 16.79
setimeout callback call: 19.29
Copy the code
Make the following table according to the fixed time interval of the console above.
Time/Type | SetTimeout Indicates the offset |
Raf offset distance |
Whether the browser draws |
---|---|---|---|
0ms | 0 | 0 | no |
16.7 ms | 0 | Shift 3.3px to the left | is |
17.79 ms | The callback executes, sets the left offset to 3.3px, and waits for the next drawing | 0 | no |
33.4 ms | Shift 3.3px to the left | Shift 3.3px to the left | is |
39.39 | The callback executes, sets the left offset to 3.3px, and waits for the next drawing | 0 | no |
50.10 ms | Shift 3.3px to the left | Shift 3.3px to the left | is |
56.18 ms | The callback executes, sets the left offset to 3.3px, and waits for the next drawing | 0 | no |
66.80 ms | Shift 3.3px to the left | Shift 3.3px to the left | is |
As can be seen from the above table, when the browser renders the fourth frame, the timer renders the displacement of three frames, but RAF keeps the same, and the deviation will be gradually larger later. A certain frame of the timer does not render, but directly renders a later frame, causing the problem of jumping. So try not to use timer animation, if you want to use JS animation, should use RAF.
The timer andraf
Difference and
In common
- Registered callbacks are macro tasks governed by event-loop
- Run in background TAB or hidden
iframe
Is paused to improve performance and battery life
Why is it controlled by event-loop? Look at the following code
moveSetTimeout(span2);
window.requestAnimationFrame(() = > moveRaf(span4));
setTiemout(() = > {
const now = performance.now();
// The js thread is directly suspended for 2000ms
while(performance.now() - now < 2000) {}},1000)
Copy the code
This is because the JS task stack is suspended for two seconds, while the JS thread and the browser UI thread are mutually exclusive, and the RAF and timer callbacks are waiting until the task stack in the JS thread is cleared. Therefore, even if raf is used for animation, such scenes need to be considered, otherwise RAF can also make very poor animation.
differences
- Raf can ensure that the callback function is executed before the next browser redraw, while the timer callback execution time is not synchronized with the browser draw
- Raf intelligently follows the screen refresh rate to ensure callbacks are executed, while timers need to be manually set at intervals
- Raf need
IE10
Supported by the above version, timer unlimited
Animations use CSS3 instead of JS as much as possible
Using Css3 instead of JS for animation has the following benefits:
- Disengaging from the JS thread (after the first rendering), even if the JS thread is suspended, does not affect the animation because the animation is running on the composite thread
- Can take advantage of hardware GPU acceleration
- Css3 animations do not trigger browser redraw rearrangements
Details can be found here: main thread and composite thread
The first point can be verified with the following code: the JS thread hangs for 2 seconds without affecting the rendering of the animation.
<! DOCTYPEhtml>
<html lang="en">
<head>
<title>Document</title>
<style>
.span1 {
height: 30px;
display: inline-block;
width: 30px;
background-color: red;
border-radius: 50%;
margin-bottom: 20px;
animation: 5s linear move;
color: white;
text-align: center;
line-height: 30px;
}
@keyframes move {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(100vw)); }}</style>
</head>
<body>
<span class="span1">1</span>
<script>
setTimeout(() = > {
let now = performance.now();
while(performance.now() - now < 2000){}
}, 1000)
</script>
</body>
</html>
Copy the code
This is why CSS animations can still execute even if the JS main thread is stuck.
The third point can be passedpeformance
Panel recording view
conclusion
Through the analysis and comparison, a better understanding of raf and timer do animation difference, along with the review of event-loop and browser threads.
Reference:
- Front-end performance optimization — Transform and Position
- Raf know how much