关于css:渲染优化之CSS-Containment

2次阅读

共计 5206 个字符,预计需要花费 14 分钟才能阅读完成。

引言

在开始介绍明天的配角 CSS Containment 之前,咱们须要理解一些前置常识回流和重绘,不便咱们了解以及利用的场景。

简略回顾下回流和重绘

  • 回流(Reflow):当浏览器必须重新处理和绘制局部或全副页面时,回流就会产生,例如元素的规模尺寸,布局,暗藏等扭转而须要从新构建。
  • 重绘(Repaint):当扭转元素的局部属性而不影响布局时,重绘就会产生。例如扭转元素的背景色彩、字体色彩等。

回流会造成什么

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.

通过翻译,咱们能够晓得,回流在性能方面耗费十分大,是很多 DOM 加载慢的起因之一。在许多状况下,它们相当于再次渲染整个页面。

接下来,来看看有哪些行为会触发回流 / 重绘。

触发回流 / 重绘

  • 增加,删除,更新 DOM 节点时会产生回流
  • 设置元素的属性为display:none 时产生回流
  • 设置元素的属性visibility: hidden 时产生重绘
  • DOM 节点上存在动画属性也将触发回流
  • 调整窗口的大小将触发回流
  • font-style 更改字体格调会扭转元素的几何形态。这意味着它可能会影响页面上其余元素的地位或大小, 触发回流
  • 增加或删除款式文件将导致回流 / 重绘
  • 通过 JavaScript 获取元素的大小等,因为须要确保获取到的值为最新的,浏览器都会先执行一次回流来保障值的正确。例如 offsetXXX_、_clientXXXscrollXXX

重绘回流优化计划

晓得了触发回流 / 重绘的起因,那么就能依据这些起因,制订相应的优化计划,如下。

  • 防止应用触发重绘回流的 CSS 属性。
  • 尽量减少 JS 操作批改 DOM 的 CSS 次数。
  • 将频繁重绘回流的 DOM 元素独自作为一个独立图层,那么这个 DOM 元素的重绘和回流影响只会在这个图层中。

通过了优化后,回流和重绘的次数曾经缩小,然而不可避免的,因为各种起因,还是会产生回流和重绘。

试想一下,有一个比较复杂的页面,当用户挪动鼠标到一个元素上,触发这个元素 hover,这个hover 的成果是使这个元素宽高产生扭转(widthheight),当元素的宽高产生扭转时,浏览器须要思考到所有元素,是否产生了相应的更改,所以浏览器须要对整个页面进行从新布局,而实际上扭转的可能只有页面的一小部分,页面大部分内容是放弃不变的。这对于性能来说,无疑是非常差的。

那么有没有一种方法,可能让浏览器进行部分的回流重绘,从而达到优化性能的目标呢?或者说,缩小回流时产生的性能耗费。答案是有的,就是明天所要意识的 CSS Containment

CSS Containment

CSS Containment 次要是通过容许开发者将某些子树从页面中独立进去,从而进步页面的性能。如果浏览器晓得页面中的某局部是独立的,就可能优化渲染并取得性能晋升。

因为有很多的交互或者简单的状况,须要触发回流,从新渲染整个页面。为了改良这个,浏览器必须辨认有哪些局部是独立的。当他们的子元素有变动时,浏览器的渲染引擎可能辨认到,只对局部元素做回流重绘,而不对整个页面进行。

辨认这个规范的属性就是 contain

contain

通过 contain 属性通知浏览器,这些节点是独立的。

语法

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.

设置了 layout 属性,就是通知浏览器以后元素外部的款式变动不会引起元素内部的款式变动。并且,元素内部的款式变动也不会引起元素外部的款式变动。这样,浏览器就能够相应的缩小渲染元素,进步渲染的性能。

如果设置了 layout 属性的元素,被遮挡,如屏幕外。则浏览器会把该元素相干的解决,放到较低的优先级中。

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

值得注意的是,因为元素外部的款式变动,导致了元素自身产生了大小等能触发回流的属性时,那么 layout 属性将不失效。

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.

设置了 paint 属性,示意这个元素的子孙节点不会在它边缘外显示。如果一个元素在视窗外或因其余起因导致不可见,则同样它的子孙节点不会被显示。

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

对于子元素,局部内容超出边界,那么该局部内容也不会被渲染。

从成果上来看,这有点相似于 overflow:hidden,不同的是 overflow:hidden,是通过将超出局部进行裁剪的形式。

举个例子,对于有滚动条的元素,因为滚动,会触发屡次渲染,这些渲染的元素,蕴含以后可视区外的元素,造成了性能节约。而应用 paint 就能够疏忽这些可视区外元素的渲染,从而达到优化渲染性能。

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 属性的元素,示意这个元素的尺寸计算不依赖于它的子孙元素的尺寸。

对于浏览器来说,设置 size 就是通知浏览器,这个元素的大小曾经固定了,就是这么大,不须要再通过重排子元素来获取以后元素的大小。

设置了 size 属性的元素,不论子元素是怎么布局,什么款式,都不会影响到父元素。

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

应用这个 size 属性,会扭转渲染的根结点,从而达到优化的目标

应用前:

应用后:

能够看到,_layout root_ 是齐全不同的,前者基于 document 整个页面,而后者是基于以后的 contain 容器元素。

在日常应用中,咱们能够对一些容器元素应用,防止因为容器外部的布局扭转,而导致整个页面的回流。

content && strict

contain:content;// 示意这个元素上有除了 size 和 style 外的所有蕴含规定。等价于 contain: layout paint。

contain:strict;// 示意除了 style 外的所有的蕴含规定利用于这个元素。等价于 contain: size layout paint。

布局

不晓得大家是否留神到,设置了 contain 的元素,只有在明确了 width, height 的状况下,才会产生成果,否则就跟失常元素一样。

真的没有其余任何变动么?其实不是的。

只有设置了 contain 的元素,就相似于应用 position:relative 布局,不同的是,z-index,以及 topleft 等扭转地位的属性对其本身是有效。

对于设置contain: layout,通过观察能够看到,观感上它与 position:relative 并无区别,都是在失常文档流中占据地位,且子元素浮于失常文档流之上。

然而,对于设置 contain: size 的元素,通过观察能够看到,它也是在失常文档流中占据地位,不同的是,子元素浮于失常文档流之下,这就能够阐明,只有设置了contain: size,它的层级是低于正常文档流的。

example

为了更直观的看出 contain 的成果,先附上 Manuel Rego Casasnovas 写的例子。

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

通过 [window.performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) 记录回流的开始工夫,在回流完结后再通过 [window.performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now) 记录一次完结工夫,用失去的开始工夫和完结工夫相减,就失去了一次残缺回流所经验的工夫。

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

通过比照 cantain: strict 设置前和设置后,能够看到性能的优化达到了 80% 左右。

在理论我的项目里下,应用 cantain: strict 属性后的成果。

截图场景,点击了 2 次按钮,残缺触发了一个模块的关上敞开,前者为应用前,后者为应用后的的理论渲染成果。

应用前:

应用后:

通过比拟,能够看出应用 cantain: strict 后,rendering 时长从 1750ms 降至 558ms,优化了 60% 左右。而 painting 时长从 230ms 降至 35ms,优化了 75% 的左右。

rendering 和 Painting 的占用工夫,都有非常明显的缩小。应用后对渲染性能的优化还是非常明显的。

兼容性

写在最初

在本次的学习中,其实还有一些值得探索或者比拟遗憾的中央:

  • contain在优化页面渲染性能的状况下,是否给浏览器带来了其余累赘?集体猜想是通过空间换工夫的形式。
  • 设计的 demo 的实际效果跟现实中的成果,并不统一,不免有些遗憾。如对于 contain:paint 来说,在屏幕外增加子节点,触发回流重绘,依据 contain:paint 属性在屏幕外,不绘制元素的个性,重绘的工夫应该是十分小,或者将近 0ms 的,然而在理论中并没有达到这个成果。

如果文章中呈现谬误,或者有更好的验证 demo,欢送留言交换哈😊。

参考文献

  • 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
正文完
 0