Rendering performance analysis (2)

Time:2019-12-10

In the last part, we have roughly analyzed the problems that need to be paid attention to in the processing of JavaScript and style. In this part, we will look at the problems that should be paid attention to in the layout, paint, composite and processing of user behavior.

Avoid large and complex layouts and layout constraints

In the layout phase, the browser will calculate the size of elements, their position in the page, the influence of other elements, etc. similar to style calculation, the basic limiting factors are as follows:

  • Number of elements requiring layout
  • Complexity of layout

TL;DR

  • Layout applies to the entire document flow
  • The number of DOM directly affects the performance consumption of layout, and try to avoid triggering layout
  • Avoid forcing synchronous modification of layout, resulting in repeated layout. That is, read the value of style and modify style

Avoid triggering layout as much as possible

When changing the style, the browser will check whether to trigger layout without recalculation. Generally speaking, modifying the geometric properties of elements, such as: width and height, layout positioning will trigger layout

.box {
  width: 200px;
  height: 200px;
}

//Change element width and height to trigger layout
.box-expanded: {
  width: 300px;
  height: 300px;
}

Layout is applied to the whole document flow, so if there are a large number of elements to be processed, it will take a long time to calculate the size and location of these elements.
If you cannot avoid triggering layout, you can use performance to check whether the time-consuming of layout phase is the bottleneck affecting performance.

Rendering performance analysis (2)

In performance, we can clearly see the time consumed in the layout phase and the number of nodes involved (as shown in the figure, 314 elements)
Https://cstriggers.com/ lists which stage some CSS properties will trigger rendering, which can be used as a reference.
In addition, using flexbox layout is faster than using float or absolute positioning.

Avoid forced synchronization layout

Normally, the rendering step is to execute JavaScript first, then style calculation and then trigger layout. But there is a situation where the time point of triggering layout is earlier than the execution of JavaScript, which is called forced synchronous layout

It is clear that the layout attribute values of the previous frame are known when JavaScript is running. For example, if you want to get the height of an element before the frame starts, you can write as follows:

requestAnimtionFrame(logBoxHeight);

function logBoxHeight(){
  console.log(element.offsetHeight);
}

But if you change the style of the element first and then get the height of the element, there will be problems

function logBoxHeight(){
  element.classList.add('big');
  console.log(element.offsetHeight);
}

Now, the situation is like this. Because the new class is added and the offset height of the element is to be input, the browser must first perform layout calculation again to get the correct offset height value, which is completely unnecessary. In this example, it is usually not necessary to set the style first and then get the attribute value, and it is sufficient to directly use the attribute value of the last frame Now. So in general, it’s better to read the required property values first, and then make changes.

function logBoxHeight(){
  console.log(element.offsetHeight);
  element.classList.add('big');
}

A worse situation is to repeatedly force synchronization to trigger layout. Look at the code below

function resizeAllParagraphsToMatchBlockWidth(){
    //Let browser fall into read-write cycle
  for(let i = 0; i < paragraphs.length; i++){
    paragraphs[i].style.width = element.offsetHeight + 'px';
  }
}

It seems that there is no problem at all. In fact, it is very common to read the element.offsetheight property every iteration, and then use it to update the width property of the paragraph. A common solution is to read one cache at a time.

const width = element.offsetHeight;

function resizeAllparagraphsToMatchBlockWidth(){
    for(let i = 0; i < paragraphs.length;i++){
        paragraphs[i].style.width = width + 'px';
    }
}

Simplify paint complexity and reduce paint area

Paint is a process of filling pixels, which are eventually synthesized to the screen through a compositor. This stage is usually the most time consuming stage in the whole process of rendering elements, so avoid it as much as possible

TL;DR

  • Any attribute other than the transform and opacity attributes will trigger paint
  • Because paint consumes the most time and performance in the whole rendering process, avoid triggering as much as possible
  • Use chrome devtools to observe the paint phase and minimize performance consumption
  • You can reduce the area size of paint by promoting layers

If you trigger layout, you will surely trigger paint, because changing the geometric attributes (width and height) of an element means that you need to relocate the layout. Of course, modifying some non geometric attributes such as background text color and shadow will also trigger paint, but layout will not be triggered, so the whole rendering process will skip the layout stage.
Rendering performance analysis (2)

Use chrome devtools to observe the most performance consuming part in the rendering process. You can see that the green part in the figure below represents the area to be redrawn.
Rendering performance analysis (2)

You can use the will change attribute or similar hack method to let the browser create a new layer to reduce the area that needs to be painted. For details about will change, see this article [what you need to know about the will change attribute] which is not covered here.

Simplify the process of paint as much as possible. Some steps in the paint stage are very performance intensive. For example, any process involving blur (for example, shadow attribute). In terms of CSS, there seems to be no performance difference between these attributes, but the actual difference in the paint stage is very obvious.

Composite

The composite phase is to aggregate the content of the paint process and display it on the screen.

There are two key factors that affect page performance in this process: one is the number of compositor layers to be integrated, and the other is the related attributes for animation

TL;DR

  • Use the will change or translatez attribute for hardware acceleration
  • Avoid creating too many layers, which will take up memory
  • Use transform and opacity to change the operation of animation

The best way to avoid triggering layout and paint during rendering is to simply process the changes in the compositing phase. To do this, you only need to always use attributes that are processed only through compositor. (only the transform and opacity attributes can do this.)

Postion   transform: translate(npx,npx);
Scale     transform: scale(n);
Rotation  transform: rotate(ndeg);
Skew      transform: skew(X|Y)(ndeg);
Matrix    transform: matrix(3d)(...);
Opacity   opacity: 0 <= n <= 1

The point of attention when using transform and opacity is that the corresponding element should be in its own composite layer, and if there is no own layer, a layer should be created. For details about creating layers and hardware acceleration, please refer to [what you need to know about the will change attribute]

It’s tempting to promote or create layers to improve performance, so it’s possible to write the following code:

* {
    will-change: transform;
    transform: translateZ(0);
}

As mentioned in [what you need to know about the will change attribute], this approach will not improve the performance, but will take up too much system resources, which will bring extra burden to both CPU and GPU.

Finally, similar to the chrome devtools we mentioned earlier, it provides a tool for developers to view page layers. You can see how many levels there are on the current page, the size of each level, the number of rendering times, and the reason for composition. We can analyze and do corresponding optimization through these information.
Rendering performance analysis (2)

Anti shake for input handler

Processing user input is also a potential performance factor because it can block the loading of other content and cause unnecessary layout work

TL;DR

  • Avoid running the processing input program for too long, which will block page scrolling;
  • Do not change the style in the program that processes the input;
  • Anti shake the input handler to store changes to event values and styles in the requestanimationframe callback of the next frame

Avoid long running processes

The fastest way of page interaction is when the user interacts with the page, the compositor thread of the page receives the user’s touch input and moves the content around. This process does not need to communicate with the main thread, but directly submits to GPU for processing. So we don’t need to wait for the main thread to finish JS processing, layout, paint and other operations.

Rendering performance analysis (2)

However, if an input handler (such as touchstart, touchmove, or touchend) is attached, the synthesizer thread must wait for the handler to finish executing, because it is possible that preventdefault() is called to prevent the touch scrolling event. Even if preventdefault() is not called, the synthesizer must wait for it to finish executing, so that the user’s scrolling operation will be blocked, which may result in frame loss and thus cause stuttering.
Rendering performance analysis (2)

All in all, you should make sure that all the input handlers you run execute quickly and allow the compositor to do its work.

Avoid changing styles in input handlers

The input handler is scheduled to run before the requestanimationframe callback. If you make style changes in this handler, there will be style processing that needs to be changed at the beginning of the request animation frame, which will trigger forced synchronization of the layout.

Rendering performance analysis (2)

Input handler anti shake

The solution to the above two problems is the same: you should be anti shake when making style changes in the callback of the next requestanimationframe.

function onScroll(evt){
    lastScrollY = window.scrollY;

    if(scheduleAnimationFrame)
        retun;
    scheduleAnimationFrame = true;
    requestAnimationFrame(readAndUpdatePage);
}

window.addEventListener('scroll',onScroll);

Another advantage of this is to keep the input handler light, because it won’t block other operations such as scrolling.

Original text: rendering performance analysis (2)

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]


    Warning: mysqli_query(): MySQL server has gone away in /www/wwwroot/developpaper.com/wp-includes/wp-db.php on line 2056

    Warning: mysqli_query(): Error reading result set's header in /www/wwwroot/developpaper.com/wp-includes/wp-db.php on line 2056

    Warning: mysqli_query(): MySQL server has gone away in /www/wwwroot/developpaper.com/wp-includes/wp-db.php on line 2056

    Warning: mysqli_query(): Error reading result set's header in /www/wwwroot/developpaper.com/wp-includes/wp-db.php on line 2056