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 0611e018053bb6visibility: 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 */
}
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;
}
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;
}
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;
}
Using this size attribute will change the root node of the rendering, so as to achieve the purpose of optimization
before use:
After use:
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.
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.
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;
}
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:
After use:
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
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 thecontain: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 😊.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。