First, take a look at a pre-16 and post-16 animation demo after Fiber

You can see an example of stack(before Fiber) where animation freezes and frames drop badly, while fiber is very silky,

To explain why stack is stalling, we need to understand the browser’s rendering mechanism in the first place

Rendering performance

60fps vs. device refresh rate

At present, the screen refresh rate of the vast majority of devices is 60 times/second, that is, the screen will refresh 60 times per second, lower than 60Hz, the human eye will obviously feel the lag, frame drop

So the screen refresh rate of 60 hz, the browser is the most ideal refresh rate for 60 times per second, or 60 frames per second, where each screen refresh, between the browser on a redraw, of course it is the ideal situation, each time to refresh re-paint is called a frame, each frame of the budget for 16 + ms, but in fact the browser has a lot of work to do, So each time the user code work is done in 10ms, if this budget is not met, the frame rate will drop and there will be stuttering

Pixel pipe

There are five main areas we need to know and pay attention to during each drawing life cycle of the browser, and these are the areas where we have the most control and are the key points from pixel to pipe screen

· JavaScript: Generally speaking, we use JavaScript to implement visual changes such as pop-ups, animations, switching routes, etc

· Style: This is the process of figuring out which elements apply which CSS rules based on the match selector

· Layout: After knowing which rules an element corresponds to, the browser can calculate how much space it should take up and where it should be on the screen. The Layout of a web page means that one element can influence other elements.

· Paint: Paint is the process of filling pixels. It involves drawing text, colors, images, borders, and shadows, basically covering every visible part of an element

· Composite: Since the various parts of the page may be drawn in multiple layers, they need to be drawn on the screen in the correct order

Each of these five steps has the potential to block and thus stall, so it’s important to know what parts our code triggers

JS/CSS > Style > Layout > Draw > Composition

If you change the geometry of an element, the browser will examine all the elements, then the page will “automatically fill”, any affected parts will be redrawn, and the final drawn elements will need to be composited

Js/CSS > Styles > Draw > Composition

If only the “paint Only “properties (e.g., color, background, etc.) are changed without affecting the layout of the page, the browser skips the layout

Js/CSS > Composition

If you modify a property that does not require layout or drawing, the browser will go directly to composition

Simulate stack in demo

Now that we know that the js execution page is rendered in the same thread, let’s implement an animation to simulate the stack demo above

<style> @keyframes boxAn{ 0%{ left:0px } 25%{ left:50px } 50%{ left:100px } 75%{ left: 150px; } 100%{ left: 200px; } } .box{ width: 400px; height: 100px; position: absolute; background: red; display: flex; flex-wrap: wrap; animation: boxAn 3s ease infinite alternate; } .box div { flex-shrink: 0; width: 20px; height: 20px; background: pink; } </style> <body> <div class="box"> </div> <div class="box2"> </div> </body> <script> let box = document.querySelector('.box'); let left = 1 let fragment = document.createDocumentFragment() for(let i = 0; i < 1000; i++) { let div = document.createElement('div'); div.innerHTML = 1; box.appendChild(div) } function sleep(date) { let flag = true; const now = Date.now(); while (flag) { if (Date.now() - now > date) { flag = false; } } } let begin = 0 let text = true function func(){ setTimeout(() => { let divs = document.querySelectorAll('.box div')  let number = begin > 10 ? (begin = 0) : begin++ divs.forEach(element => { element.textContent = number }); // sleep(500) if(! (left > 500)) { text = ! text ++left func() } },200) } func() </script>Copy the code

You can see this when sleep is not called

Except for the initial script, the FPS is always green and the picture is smooth, and then we let go of Sleep

As you can see, the FPS at this point is mostly 1, 2 animation is very slow, and the thread is mostly occupied by timer tasks.

This is exactly what Dan Abramov said in his talk:

When dealing with UIs, the problem is that if too much work is executed all at once, It can cause animations to drop frames... The problem when dealing with UIs is that if a lot of work is done at once, it can cause animation frames to drop...Copy the code

When react performs updates, it synchronously traverses the entire tree of components, rendering updates for each component. As a result, when the tree is large, the code takes longer than 16ms to execute, causing the browser to stall and drop frames

requestIdleCallback

The browser has a solution for this problem, requestIdleCallback,

API:

Var handle = window. RequestIdleCallback (callback [options]) callback: an event loop in spare time is of the called function reference. The function receives a parameter called IdleDeadline, which gets the status of the current idle time and whether the callback has been executed before the timeout. The IdleDeadline object contains didTimeout, which is a Boolean value to indicate whether the task times out and is used in conjunction with timeRemaining. TimeRemaining (), which represents the timeRemaining in the current frame. It can also be interpreted as how much time is left for the task. Options parameter timeout: indicates that if a task is not executed after the timeout period expires, the task is forcibly executed. If the callback has not been invoked by timeout milliseconds, it will be enforced during the next idle period. If the callback is explicitly executed within a certain period of time, you can set the timeout value. When the browser is busy, requestIdleCallback times out just like setTimeout.Copy the code

Chrome says this API call time looks like this:

RequestIdleCallback will have idle time at the end of the frame, or schedule work when the user is inactive. This means you have a chance to get your work done without getting in the way of your users.

That is, there are two scenarios that call requestIdleCallback:

1. The render time of the current browser is less than the screen refresh time (for 60Hz devices, that is, less than 16ms) and the idle time between the start of the next frame rendering

Note: this is not called if the current frame has no free time

2. There is no rendering task in the current browser, the main thread is idle, and the event queue is empty. In order to avoid noticeable delays caused by users in unpredictable tasks (such as processing of user input), the length of these idle cycles should be limited to a maximum of 50ms. Namely IdleDeadline. TimeRemaining () does not exceed 50 largest,

Note: The RESPONSETIME maximum is 50 milliseconds and is based on research showing that responses to user input within 100 milliseconds are generally considered instantaneous to humans, i.e. undetectable to humans. Setting the idle deadline to 50ms means that even if user input occurs immediately after the idle task starts, the user agent still has a remaining 50ms in which to respond to user input without perceptible lag to the user.

Here’s a summary:

The requestIdleCallback is executed at the end of each frame to determine whether the browser is idle. If the browser is always occupied, there is no idle time, and if requestIdleCallback is not set to timeout, the callback task will always be delayed. If timeout is set in the current frame, the browser will determine whether to execute the callback after the current frame ends.

Dom manipulation in requestIdleCallback is not recommended by browsers:

1. Browsers may be too busy to run any callbacks in a given frame, so you shouldn’t expect to have any free time to do more work at the end of the frame

2. Changing the DOM can cause unpredictable execution times because it triggers style calculations, layout, drawing, and composition

3. If the callback is triggered at the end of the frame, it will be scheduled to start after the current frame is submitted, which means that the changed layout will not be valid. If the next frame is read with any type of layout, such as getBoundingClientRect, clientWidth, etc., the browser will have to perform mandatory synchronous layout. This is a potential performance bottleneck.

Best practice is to make DOM changes inside the requestAnimationFrame callback (which tells the browser that you want to execute an animation and requires the browser to call the specified callback function to update the animation before the next redraw). If you are using the VDOM library, Changes can be made using requestIdleCallback, but DOM patches can be applied in the next requestAnimationFrame callback instead of the idle one

React is a polyfill based on requestAnimationFrame.

Why the fiber

First, let’s look at how React works

When React receives an update, it constructs it layer by layer, element => instances => domNode, and calls it recursively up to the bottom of the component tree. So if you see these recursive calls in Chrome’s development tool timeLine(now gone), you’ll see an image like this

So when you’re on a recursive call stack, the main thread is blocked by JS, causing frames to drop, as in the demo above,

So, the question is isn’t React fast enough?

No, it’s not. The user’s code is outside react and is out of control, so react faster isn’t enough to solve the problem

With the background above, the browser redraw is stuck behind these larger updates,

The solution is to play updates, which combine updates from the browser, such as CSS animations, and browser resizing, to determine who updates first by priority

Different updates have different priorities, such as high priority: user input response, low priority: data from the server

So fiber takes tasks apart and prioritizes them, but it also tracks how much time has passed

Causes the current thread to evaluate a small part of the tree and then goes back to check to see if anything else is working

Fiber architecture has two main phases: Reconciliation/Render, and Commit

The Reconciliation phase is interruptible, and the commit phase is non-interruptible

During the reconciliation stage, the following things are done

· Update state and props

· Call lifecycle hooks

· In the parent component, the child component (class component) is obtained by calling render method, and the function component is directly obtained by calling render method

· Compare the resulting subcomponent to the previously rendered component, i.e., diff

· Calculate the DOM that needs to be updated

The context for the new coordination algorithm is a data structure called fiber

A filber is just a simple object

With fiber’s linked list structure, React can notify traversal operations at any time and start operations at any time. In combination with requestIdleCallback, react can implement the above scheduling tasks

Reference links:

www.youtube.com/watch?v=ZCu…

Developers.google.com/web/fundame…

Developers.google.com/web/updates…

Juejin. Cn/post / 684490…

Juejin. Cn/post / 684490…