Fluency
This article is based on the replica of "Make Your Website Smoother by Liu Bowen" on FDCon 2019. This topic is also an area of interest to bloggers, and will be further integrated with React's Schedule. Personal blog
- Passive interaction: animation
- Active Interaction: Mouse, Keyboard
Passive interaction
At present, the frequency of equipment on the market is over 60 HZ.
Active interaction
Run the following interface https://code.h5jun.com/pojob
Combining the following code blocks, you can see that clicks below 100ms are smooth, and clicks over 100ms will have cartoon phenomenon.
var observer = new PerformanceObserver(function(list) { var perfEntries = list.getEntries() console.log(perfEntries) }); observer.observe({entryTypes: ["longtask"]});
Make users feel fluent
There's a good way to measure whether a web page / App is fluent or not. Rail model It has the following criteria.
Response - 100ms Animation - 16.7ms Idle - 50ms Load - 1000ms
Pixel pipeline
Pixel pipes generally consist of five parts. JavaScript, Style, Layout, Drawing, Composition. As shown in the following figure:
Ensure active interaction to make users feel fluent
function App() { useEffect(() => { setTimeout(_ => { const start = performance.now() while (performance.now() - start < 1000) { } console.log('done!') }, 5000) }) return ( <input type="text" /> ); }
Generally over 50 ms is considered a long task. Long task blocks the operation of main thread. Here are two solutions.
Web Worker
The app.js code is as follows:
import React, {useEffect} from 'react' import WorkerCode from './worker' function App() { useEffect(() => { const testWorker = new Worker(WorkerCode) setTimeout(() => { testWorker.postMessage({}) testWorker.onmessage = function(ev) { console.log(ev.data) } }, 5000) }) return ( <input type="text" /> ); }
The worker.js code is as follows:
const workerCode = () => { self.onmessage = function() { const start = performance.now() while (performance.now() - start < 1000) { } postMessage('done!') } }
At this time, there is no sense of Carton when the input box is entered.
Time Slicing
Here's another way to make the page flow smoothly: Time Slicing.
Look at the Performance of Chrome. The flame diagram is as follows.
As can be seen from the flame diagram, the main thread is split into several time slices, so it does not cause a jamming. The code snippet for time slicing is as follows:
function timeSlicing(gen) { if (typeof gen === 'function') gen = gen() if (!gen || typeof gen.next !== 'function') return (function next() { const res = gen.next() // ① if (res.done) return // ⑤ setTimeout(next) // ③ })() } // Calling time slicing function timeSlicing(function* () { const start = performance.now() while (performance.now() - start < 1000) { console.log('Execution logic') yield // ② } console.log('done') // ④ })
Although the code size of this function is not long, it is not easy to understand. Pre knowledge Generator
Following is an analysis of the function:
- The generator function is passed into the time slicing function timeSlicing.
- Function execution sequence - 1, 2, 3, 1 (there is a competition relationship at this time, if performance. now () - start < 1000 then continue to 2, 3, if performance. now () - start >= 1000 then jump out of the loop execution 4, _);
conclusion
In view of the situation that long task will block the operation of main thread, two solutions are given:
- Web Worker: Use the multi-threaded environment provided by Web Worker to handle long task s;
- Time Slicing: Time slicing the long task on the main thread;
Ensure that passive interaction makes users feel fluent
Ensure a new frame of 16.7ms is transmitted to the interface. Apart from the user's logic code, the integration time of browsers in a frame is only about 6 ms. When we return to the pixel pipeline, we can optimize from these aspects:
Avoid too deep nesting of CSS selectors
The optimization of Style is used in the css style selector. The more levels the css selector uses, the more time it takes. The following is a test result of screening the same elements at different levels of the css selector.
div.box:not(:empty):last-of-type span 2.25ms index.html:85 .box--last span 0.28ms index.html:85 .box:nth-last-child(-n+1) span 2.51ms
Avoid layout rearrangement
// Modify value first el.style.witdh = '100px' // Post value const width = el.offsetWidth
What's wrong with this code?
You can see that it causes layout rearrangement.
The strategy is to adjust their execution order.
// Prefix value const width = el.offsetWidth // Post modification value el.style.witdh = '100px'
You can see that the el.style.width executed after the conversion sequence will open a new pixel pipeline instead of rearranging the original pixel pipeline.
In addition, do not perform the following operations in a loop.
for (var i = 0; i < 1000; i++) { const newWidth = container.offsetWidth; // ① boxes[i].style.width = newWidth + 'px'; // ② }
You can see in the flame diagram that it has been redrawn.
If we insert a vertical line after the first one, then it will become a situation of modifying the value first and then taking the value, so a redrawing will take place!
The correct posture should be as follows:
const newWidth = container.offsetWidth; for (var i = 0; i < 1000; i++) { boxes[i].style.width = newWidth + 'px'; }
Avoid repainting
Creating Layers avoids redrawing.
{ transform: translateZ(0); }