4

introduction

Before we start to introduce today's protagonist CSS Containment , we need to understand some pre-knowledge reflow and redraw to facilitate our understanding and application scenarios.

Reflow and redraw under simple memories

  • Reflow: When the browser must reprocess and draw part or all of the page, reflow will occur, such as the size, layout, and hiding of elements that change and need to be rebuilt.
  • Repaint: Repainting occurs when part of the attributes of an element is changed without affecting the layout. For example, change the background color and font color of the element.

What will reflow cause

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.

Through translation, we can know that reflow is very expensive in terms of performance, which is one of the reasons for the slow loading of many DOMs. In many cases, they are equivalent to rendering the entire page again.

Next, let's take a look at what behaviors will trigger reflow/redraw.

Trigger reflow/redraw

  • Reflow occurs when adding, deleting, and updating DOM nodes
  • display:none occurs when the attribute of the element is set to 0611e018053bb6
  • visibility: hidden occurs when setting the element's attribute 0611e018053bd2
  • The presence of animation attributes on DOM nodes will also trigger reflow
  • Resizing the window will trigger reflow
  • font-style Changing the font style will change the geometric shape of the element. This means it may affect the position or size of other elements on the page, triggering reflow
  • Adding or deleting style files will cause reflow/redraw
  • Obtain the size of the element through JavaScript, etc. Since it is necessary to ensure that the obtained value is the latest, the browser will first perform a reflow to ensure that the value is correct. For example, offsetXXX_, _clientXXX and scrollXXX etc.

Redraw the reflow optimization plan

Knowing the reasons for triggering reflow/redrawing, then we can formulate corresponding optimization schemes based on these reasons, as follows.

  • Avoid using CSS properties that trigger redraw reflow.
  • Minimize the number of times that JS operations modify the CSS of the DOM.
  • The DOM element that is frequently redrawn and reflowed is used as a separate layer, and the redrawing and reflow of this DOM element will only affect this layer.

After optimization, the number of reflows and redraws has been reduced, but it is inevitable that reflows and redraws will still occur due to various reasons.

Imagine a more complicated page. When the user moves the mouse on an element, the element hover triggered. The hover is to change the width and height of the element ( width , height ), when the width and height of the element change , The browser needs to consider whether all elements have been changed accordingly, so the browser needs to re-layout the entire page, but in fact, only a small part of the page may be changed, and most of the content of the page remains unchanged. This is undoubtedly very poor for performance.

So is there a way to allow the browser to perform partial reflow and redraw, so as to achieve the purpose of optimizing performance? In other words, reduce performance consumption during reflow. The answer is yes, it is the CSS Containment

CSS Containment

CSS Containment mainly improves the performance of the page by allowing developers to separate certain subtrees from the page. If the browser knows that a certain part of the page is independent, it can optimize the rendering and get a performance increase.

Due to many interactions or complex situations, it is necessary to trigger a reflow and re-render the entire page. In order to improve this, the browser must recognize which parts are independent. When their child elements change, the rendering engine of the browser can recognize that only part of the elements are reflowed and redrawn instead of the entire page.

The attribute that recognizes this standard is contain .

contain

Tell the browser through the contain attribute that these nodes are independent.

grammar

div {
  contain: none; /* 表示元素将正常渲染,没有包含规则 */
  contain: layout; /* 表示元素外部无法影响元素内部的布局,反之亦然 */
  contain: paint; /* 表示这个元素的子孙节点不会在它边缘外显示。如果一个元素在视窗外或因其他原因导致不可见,则同样保证它的子孙节点不会被显示。 */
  contain: size; /* 表示这个元素的尺寸计算不依赖于它的子孙元素的尺寸 */
  
  contain: content; /* 等价于 contain: layout paint */
  contain: strict; /* 等价于 contain: size layout paint */
}

An example

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.

Setting the layout attribute tells the browser that the style change inside the current element will not cause the style change outside the element. In addition, the style change outside the element will not cause the style change inside the element. In this way, the browser can reduce rendering elements accordingly and improve rendering performance.

If the element with the layout attribute is set, it is blocked, such as off-screen. Then the browser will put the processing related to the element to a lower priority.

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

file

It is worth noting that, due to changes in the style of the element, the size of the element itself and other attributes that can trigger reflow, the layout attribute will not take effect.

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 attribute is set, which means that the descendant nodes of this element will not be displayed outside its edge. If an element is invisible outside the window or due to other reasons, its descendants will not be displayed.

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

file

For child elements, if part of the content exceeds the boundary, then the part of the content will not be rendered.

From the effect point of view, this is a bit similar to overflow:hidden , the difference is overflow:hidden , which is by cutting the excess part.

For example, for an element with a scroll bar, due to scrolling, multiple renderings will be triggered. These rendered elements include elements outside the current viewable area, causing performance waste. Using paint can ignore the rendering of these elements outside the visible area, so as to optimize the rendering performance.

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.

size set indicates that the calculation of the size of this element does not depend on the size of its descendants.

For the browser, setting size is to tell the browser that the size of this element has been fixed, it is so large, there is no need to rearrange the child elements to get the size of the current element.

The element with the size attribute set will not affect the parent element regardless of the layout or style of the child element.

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

file

Using this size attribute will change the root node of the rendering, so as to achieve the purpose of optimization

before use:

file

After use:

file

As you can see, _layout root_ is completely different. The former is based on the document entire page, while the latter is based on the current contain container element.

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

content && strict

contain: content; // Indicates that this element has all the containment rules except size and style. Equivalent to contain: layout paint.
contain: strict; // Indicates that all containment rules except style apply to this element. Equivalent to contain: size layout paint.

layout

I do not know if you noticed, set contain elements, only in clear width , height case, will have an effect, or just the same as normal elements.

Are there really no other changes? Actually not.

As long as contain is set, it is similar to using the position:relative . The difference is that the z-index , top , left etc. are invalid for themselves.

Regarding the setting contain: layout , through observation, it can be seen that it position:relative , both occupy a position in the normal document flow, and the sub-elements float above the normal document flow.

file

However, for contain: size , it can be seen by observation that it also occupies a position in the normal document flow. The difference is that the child element floats under the normal document flow. This can explain that as long as contain: size set, it is The level is lower than the normal document flow.

file

example

To see the effect contain more intuitive, first attach Manuel Rego Casasnovas example written.

window.performance.now() // 返回一个表示从性能测量时刻开始经过的毫秒数

By [window.performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) recording start returning time after the end back through the [window.performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) recorded once the end of time, with the start time and end time to get subtract, you get a full time experienced reflux.

function runTests() {
    setup(); // 创建 1000 个节点

    let avg1 = changeTargetContent(); // 没有设置contain,触发回流

    let targetItem = document.getElementById('targetItem');
    targetItem.style.contain = 'strict';
    let avg2 = changeTargetContent(); // 触发回流
}

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;
}

file

By comparing cantain: strict before and after setting, we can see that the performance optimization has reached about 80%.

In the actual project, the effect after cantain: strict

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:
file

After use:
file

By comparison, it can be seen that cantain: strict , the rendering time is reduced from 1750ms to 558ms, which is about 60% optimized. The painting time has been reduced from 230ms to 35ms, which is about 75% optimized.

The time taken for rendering and painting has been significantly reduced. The optimization of rendering performance after use is still very obvious.

compatibility

file

Write at the end

In this study, there are actually some points worth exploring or regrettable:

  • contain In the case of optimizing page rendering performance, does it bring other burdens to the browser? Personal guess is the way of changing time through space.
  • The actual effect of the designed demo is inconsistent with the ideal effect, which is a pity. For contain:paint , for 0611e0180549f7, add a child node outside the screen to trigger a reflow redraw. According to the contain:paint outside the screen, no element is drawn, the redrawing time should be very small, or nearly 0ms, but in practice it is not Did not achieve this effect.

If there is an error in the article, or if there is a better verification demo, please leave a message and exchange it 😊.

references


袋鼠云数栈UED
286 声望38 粉丝

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。