fluency

This post is based on FDCon2019’s “Make your Web smoother by Bowen Liu” reprint. This topic is also an area of interest for bloggers, and will be further integrated with the React Schedule later.

To ensure real-time and accurate content, you can follow personal blogs

  • Passive interaction: Animation
  • Active interaction: mouse, keyboard

Passive interaction

Current devices on the market have frequencies above 60 HZ.

Active interaction

Run the following interface code.h5jun.com/pojob

Combined with the following code block, you can see that clicks below 100ms are smooth, while clicks beyond 100ms will stall.

var observer = new PerformanceObserver(function(list) {
  var perfEntries = list.getEntries()
  console.log(perfEntries)
});
observer.observe({entryTypes: ["longtask"]});
Copy the code

Let the user feel smooth

There is a good Rail model for measuring the smoothness of a web page /App, and it probably has the following criteria.

Response —— 100ms
Animation —— 16.7ms
Idle —— 50ms
Load —— 1000ms
Copy the code

Pixel pipe

The pixel pipeline generally consists of five parts. JavaScript, styles, layout, drawing, composition. As shown below:

Rendering performance

Ensure that active interaction feels smooth to the user

function App() {
  useEffect((a)= > {
    setTimeout(_= > {
      const start = performance.now()
      while (performance.now() - start < 1000) {}console.log('done! ')},5000)})return (
    <input type="text" />
  );
}
Copy the code

Generally, a long task that exceeds 50 ms is considered to block the running of the main thread. The following two solutions are available.

Web Worker

The app.js code is as follows:

import React, {useEffect} from 'react'
import WorkerCode from './worker'

function App() {
  useEffect((a)= > {
    const testWorker = new Worker(WorkerCode)
    setTimeout((a)= > {
      testWorker.postMessage({})
      testWorker.onmessage = function(ev) {
        console.log(ev.data)
      }
    }, 5000)})return (
    <input type="text" />
  );
}
Copy the code

The worker.js code is as follows:

const workerCode = (a)= > {
  self.onmessage = function() {
    const start = performance.now()
    while (performance.now() - start < 1000) { }
    postMessage('done! ')}}Copy the code

There is no stutter in the input box.

Time Slicing

Here’s another way to make your page smooth — Time Slicing.

Looking at Chrome’s Performance, the fire chart looks like this,

From the flame diagram, you can see that the main thread is split into multiple time slices, so there is no lag. The time-sharding code snippet looks like this:

function timeSlicing(gen) {
  if (typeof gen === 'function') gen = gen()
  if(! gen ||typeofgen.next ! = ='function') return

  (function next() {
    const res = gen.next() / / 1.
    if (res.done) return / / 5.
    setTimeout(next) / / 3.}}) ()// Call the time fragmentation function
timeSlicing(function* () {
  const start = performance.now()
  while (performance.now() - start < 1000) {
    console.log('Execute logic')
    yield / / 2.
  }
  console.log('done') / / 4.
})
Copy the code

This function is short in code, but not easy to understand. Preknowledge Generator

The function is analyzed as follows:

  1. Shard function over timetimeSlicingThe incominggeneratorFunctions;
  2. The order of execution of the functions — ①, ②, ③, ① (there is a race relationship, ifperformance.now() - start < 1000Then continue ②, ③, ifperformance.now() - start >= 1000Then jump out of the loop execute ④, ⑤);

conclusion

If the long Task blocks the main thread, there are two solutions:

  • Web WorkerUse:Web WorkerProvides a multithreaded environment to handlelong task;
  • Time Slicing: will the main threadlong taskTime fragmentation;

Make passive interaction feel smooth to the user

Ensure that a new frame is transferred to the interface at 16.7ms. Excluding the user’s logic code, the time left for the browser to integrate is around 6ms per frame. Going back to the pixel pipeline, we can optimize in these areas:

Avoid deep nesting of CSS selectors

The Style part of the optimization in the CSS Style selector use, the CSS selector use more layers, more time. Here are the results of a test that tested the filtering of the same element at different levels of CSS selectors.

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.51 msCopy the code

Avoid layout rearrangement

// Change the value first
el.style.witdh = '100px'
/ / value
const width = el.offsetWidth
Copy the code

What’s wrong with this code?

As you can see, it rearranges the layout.

The strategy is to adjust their order of execution,

/ / value first
const width = el.offsetWidth
// Change the value
el.style.witdh = '100px'
Copy the code

You can see that by switching the order, the last execution of el.style.width will open a new pixel pipe instead of rearranging the original pixel pipe.

Also do not perform the following operations in the loop,

for (var i = 0; i < 1000; i++) {
  const newWidth = container.offsetWidth; / / 1.
  boxes[i].style.width = newWidth + 'px'; / / 2.
}
Copy the code

You can see in the fire chart that it has a redraw warning,

The execution sequence is ①②①②① ① ① ① ① ①②① ① ①… If we behind the first one (1) after insert a vertical bar (1) | 2. (1) (2) (1) (2) (1), it becomes the first value after the modification of the value, so also redraw happened!

The correct posture should be as follows:

const newWidth = container.offsetWidth;
for (var i = 0; i < 1000; i++) {
  boxes[i].style.width = newWidth + 'px';
}
Copy the code

Avoid to redraw

Create Layers to avoid redrawing,

{
  transform: translateZ(0);
}
Copy the code