Page fluency with FPS
The page is drawn frame by frame, and the page is smooth when the number of frames drawn per second (FPS) reaches 60, below which the user feels stuck.
1S is 60 frames, so the time allotted to each frame is 1000/60 ≈ 16 ms. So we try to write code that doesn’t take more than 16ms per frame.
Frame
So what does the browser need to do for each frame?
As can be seen from the figure above, tasks of the following six steps need to be completed within a frame:
- Handle user interactions
- JS parsing execution
- Start frame. Window size change, page roll away, etc
- requestAnimationFrame(rAF)
- layout
- draw
requestIdleCallback
The task registered in requestIdleCallback will be executed if it does not exceed 16 ms after the above six steps are completed.
As you can also see from the figure above, unlike requestAnimationFrame, which is bound to execute every frame, requestIdleCallback picks up the browser idle to execute the task.
As a result, if the browser is always very busy, the task registered by requestIdleCallback may never execute. You can set timeout (see API below) to ensure execution.
API
var handle = window.requestIdleCallback(callback[, options])
Copy the code
- callbackThe: callback, which is a task that needs to be performed when idle, receives one
IdleDeadline
Object as an input parameter. Among themIdleDeadline
The object contains:didTimeout
, Boolean value indicating whether the task times outtimeRemaining
Use.timeRemaining()
Represents the time remaining in the current frame, which can also be understood as the amount of time left for the task.
- options: Currently, options has only one parameter
timeout
. If the task is not executed after the specified time, the task is forcibly executed.
IdleDeadline object reference MDN:developer.mozilla.org/zh-CN/docs/…
The sample
requestIdleCallback(myNonEssentialWork, { timeout: 2000 }); Const tasks = [() => {console.log()"First mission.");
},
() => {
console.log("Mission Two.");
},
() => {
console.log("Mission Three."); },];functionMyNonEssentialWork (deadline) {// If the frame has extra time, or a timeoutwhile ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
work();
}
if (tasks.length > 0)
requestIdleCallback(myNonEssentialWork);
}
function work () {
tasks.shift()();
console.log('Perform the task');
}
Copy the code
The dealine didTmieout is true and timeRemaining () returns 0. In this case, the browser is busy and has no idle time. If you continue with a timeout, you’re bound to get stuck, because you’re bound to stretch out a frame.
cancelIdleCallback
Similar to setTimeout, returns a unique ID that can be cancelled by cancelIdleCallback.
conclusion
Some low-priority tasks can be performed when the browser is not busy, such as requestIdleCallback, and because time is limited, the tasks it performs should be quantified and subdivided micro tasks.
Because it happens at the end of a frame, when the page layout is complete, it is not recommended to manipulate the DOM in the requestIdleCallback, which will cause the page to be redrawn again. DOM manipulation is recommended in rAF. At the same time, the time required to manipulate the DOM is uncertain because it leads to recalculation of the layout and drawing of the view, so such operations are not predictable.
Promise is not recommended to do this either, because the higher-priority microtask in the Promise callback property Event loop will be inrequestIdleCallback
Execute immediately at the end of the frame, regardless of whether there is time to spare, which will most likely take a frame over 16 ms.
As a bonuswindow.requestAnimationFrame
In the absence of a requestAnimationFrame method, we might use setTimeout or setInterval to trigger visual changes to perform the animation; The problem with this approach is that the time of the callback is not fixed, it may be right at the end, or it may not be executed at all, often resulting in frame loss and page stalling.
The root cause of this problem is timing, when the browser needs to know when to respond to the callback function. SetTimeout or setInterval uses a timer to trigger a callback function. However, a timer cannot be guaranteed to execute correctly. There are many factors that affect its running time, such as: When a synchronous code is executed, the system waits until the synchronous code is finished and there are no other tasks in the asynchronous queue. Also, we know that the optimal time for each re-render is about 16.6ms. If the timer interval is too short, it will cause overrendering and increase overhead. Too long will delay rendering and make the animation not smooth.
RequestAnimationFrame differs from setTimeout or setInterval in that it is up to the system to determine when the callback should be executed, asking the browser to execute the callback before the next rendering. Regardless of the refresh rate of the device, the requestAnimationFrame interval follows the time it takes to refresh the screen once; For example, if the refresh rate of a device is 75 Hz, then the time interval is 13.3 ms (1 second / 75 times). Note that this method ensures that the callback function is rendered only once per frame, but if there are too many tasks in that frame, it will still cause a lag. Therefore, it can only ensure that the minimum interval between rerenders is the screen refresh time.
The requestAnimationFrame method is described in the MDN documentation, and an example of a web animation is used to see how it can be used.
let offsetTop = 0;
const div = document.querySelector(".div");
const run = () => {
div.style.transform = `translate3d(0, ${offsetTop += 10}px, 0)`;
window.requestAnimationFrame(run);
};
run();
Copy the code
If you want to animate, you must call the requestAnimationFrame method again each time the callback function is executed. In the same way as setTimeout does, but without the time interval.
Refer to the article
Page rendering performance optimization – Performance optimization