The introduction

Before we get into CSS Containment, we need to know a little bit about backflow and redraw to understand and apply scenarios.

Just a quick reminder of backflow and redraw

  • Reflow: This occurs when the browser must reprocess and redraw part or all of the page, such as the size, layout, hiding of elements that need to be rebuilt.
  • Repaint: Repaint occurs when some attributes of an element are changed without affecting the layout. For example, change the background color of elements, font color, etc.

What does backflow do

Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, In many cases, they are equivalent to laying out the entire page again.

As we can see from the translation, backflow is very costly in terms of performance and is one of the reasons why many DOM loads are slow. In many cases, they are equivalent to rendering the entire page again.

Next, take a look at some of the actions that trigger backflow/redraw.

Trigger backflow/redraw

  • Backflow occurs when DOM nodes are added, deleted, or updated
  • Sets the attribute of the element todisplay:noneWhen reflux occurs
  • Sets the attributes of the elementvisibility: hiddenRedraw occurs when
  • The presence of animated properties on DOM nodes also triggers backflow
  • Resize the window triggers backflow
  • font-styleChanging the font style changes the geometry of the element. This means that it can affect the position or size of other elements on the page, triggering backflow
  • Adding or removing style files will cause backflow/redraw
  • Getting the size of an element in JavaScript, for example, requires the browser to perform a backflow to ensure the value is up to date. For example,offsetXXX,clientXXXscrollXXX

Redraw reflux optimization scheme

Now that you know what triggers backflow/redraw, you can make an optimization based on those reasons, as follows.

  • Avoid using CSS properties that trigger redraw backflow.
  • Minimize the number of CSS changes to the DOM by JS operations.
  • If the DOM element that is frequently redrawn is a separate layer, the redrawn and redrawn effects of that DOM element will only be on that layer.

After optimization, the number of backflow and redraw has been reduced, but inevitably, for various reasons, backflow and redraw still occur.

Imagine, there is a complex page, when the user moves the mouse over an element, trigger the element hover, the hover effect is to change the width and height of the element, when the width and height of the element changes, the browser needs to consider all elements, whether the corresponding change. So the browser needs to rearrange the entire page, but it may change only a small part of the page, leaving most of the page unchanged. This is certainly very poor performance.

Is there a way to optimize performance by having the browser partially redraw the flow? In other words, reduce the performance cost of backflow. And the answer is yes, CSS Containment for today

CSS Containment

CSS Containment is used primarily to improve page performance by allowing developers to isolate certain subtrees from the page. If the browser knows that parts of the page are independent, it can optimize rendering and get performance gains.

Because there is a lot of interaction or complexity, you need to trigger backflow and re-render the entire page. To improve this, the browser must recognize which parts are independent. When their child elements change, the browser’s rendering engine recognizes them and redraws only some elements, not the entire page.

Contain is an attribute that identifies this standard.

contain

The contain attribute tells the browser that these nodes are independent.

grammar

div {
  contain: none; /* indicates that the element will render normally, without rules */
  contain: layout; /* Indicates that the outside of the element cannot affect the layout of the inside of the element, and vice versa */
  contain: paint; /* Indicates that descendants of this element are not displayed outside its edge. If an element is not visible outside the window or for other reasons, its descendants are also guaranteed not to be displayed. * /
  contain: size; /* indicates that the size calculation of this element does not depend on the size of its descendants */
  
  contain: content; /* Equivalent to contain: layout */
  contain: strict; Contain: contain size layout paint */
}
Copy the code

A case in point

Layout

This value turns on layout containment for the element. This ensures that the containment box is totally opaque for layout purposes; nothing outside can affect its internal layout, and vice versa.

The layout property tells the browser that style changes inside the current element will not cause style changes outside the element. Also, style changes outside the element do not cause style changes inside the element. In this way, the browser can reduce rendering elements and improve rendering performance accordingly.

If an element with the layout property is occluded, as out of the screen. The browser places the processing associated with this element in a lower priority.

.container li {
    padding: 10px;
    height: 100px;
    
    contain: layout;
}
Copy the code

It is important to note that the Layout property does not take effect when the element itself has a size that triggers backflow due to style changes within the element.

Paint

This value turns on paint containment for the element. This ensures that the descendants of the containment box don’t display outside its bounds, so if an element is off-screen or otherwise not visible, its descendants are also guaranteed to be not visible.

The paint property is set to indicate that descendants of this element are not displayed outside its edges. If an element is not visible outside the window or for other reasons, its descendants will not be displayed either.

.container li {
    padding: 10px;
    height: 100px;
    
    contain: paint;
}
Copy the code

For child elements, if part of the content is out of bounds, that part of the content will not be rendered.

In effect, this is similar to Overflow: Hidden, except that Overflow: Hidden is trimmed out by the overlays.

For example, for elements with scrollbars, multiple renderings are triggered due to scrolling, and the rendered elements contain elements outside the current viewable area, resulting in a performance waste. With Paint, you can optimize rendering performance by ignoring these out-of-viewable elements.

Size

The value turns on size containment for the element. This ensures that the containment box can be laid out without needing to examine its descendants.

Element with the size attribute, indicating that the size calculation of this element does not depend on the size of its descendants.

To the browser, setting size tells the browser that the size of the element is fixed, that it is that large, and there is no need to rearrange the child elements to get the size of the current element.

The size attribute does not affect the parent element, regardless of the layout or style of the element.

.container li {
    padding: 10px;
    height: 100px;
    
    contain: size;
}
Copy the code

Using the size attribute, the render root is changed for optimization purposes

Before use:

After use:

As you can see, layout root is completely different. The former is based on the entire Document page, while the latter is based on the current folder element.

In daily use, we can use some container elements to avoid the whole page backflow due to changes in the layout of the container.

content && strict

Contain: the content; // Indicates that this element has all inclusion rules except size and style. Contain: layout paint

Contain: strict; // All inclusion rules except style apply to this element. Contain: contain size layout paint

layout

Contain element (width, height) contains element (width, height); contain element (height, width, height)

Are there really no other changes? It’s not.

Contain elements are similar to position: relative layout, except that z-index, top, left and other attributes that change the position of a folder are not included.

Contain: layout = position: relative; contain: layout = position: relative; contain: layout = position: relative;

Contain: size; contain: size; contain: size; contain: size; contain: size; contain: size; contain: size; contain: size

example

My presentation contains an example written by Manuel Rego Casasnovas.

window.performance.now() // Returns a number of milliseconds since the time of the performance measurement
Copy the code

Through [window. The performance. Now ()] (https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) record return the beginning of time, Again at the end of the reflux by [window. The performance. Now ()] (https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) recording an end time, Subtracting the resulting start time and end time gives the time taken for a complete reflux.

function runTests() {
    setup(); // Create 1000 nodes

    let avg1 = changeTargetContent(); // no folder is contained

    let targetItem = document.getElementById('targetItem');
    targetItem.style.contain = 'strict';
    let avg2 = changeTargetContent(); // Trigger backflow
}

function changeTargetContent() {
    // Force layout.
    document.body.offsetLeft;

    let start = window.performance.now();

    let targetInner = document.getElementById('targetInner');
    targetInner.textContent =
        targetInner.textContent == 'Hello World! '
            ? 'BYE'
            : 'Hello World! ';

    // Force layout.
    document.body.offsetLeft;

    let end = window.performance.now();
    let time =end - start;
    return time;
}
Copy the code

Comparing before and after the cantain: strict setting, you can see that the performance optimization is about 80%.

In a real project, use the cantain: strict property.

In the screenshot scene, clicking the button twice completely triggered the opening and closing of a module. The former is before use and the latter is the actual rendering effect after use.

Before use:

After use:

By comparison, the rendering time was reduced from 1750ms to 558ms after using Cantain: Strict, which was about 60% optimized. The painting time was reduced from 230ms to 35ms, which was about 75% optimized.

Rendering and Painting takes significantly less time. The optimization of rendering performance after use is very obvious.

compatibility

Write in the last

In this study, in fact, there are some worthy of exploration or regret:

  • containIs there any additional burden on the browser when optimizing page rendering performance? Personal guess is a way of exchanging space for time.
  • The actual effect of the demo is not consistent with the ideal effect, which is regrettable. As for theContain: paintTo add child nodes off-screen, trigger reflux redraw, according toContain: paintProperties are off-screen, do not draw elements, and the redraw time should be very small, or close to 0ms, but this is not achieved in practice.

If there is an error in this article, or you have a better verification demo, please leave a comment at 😊.

reference

  • CSS Containment
  • CSS Containment in Chrome 52
  • CSS-Tricks
  • caniuse
  • An introduction to CSS Containment
  • CSS Containment Module Level 1
  • CSS Containment Module Level 2