引言

在开始介绍今天的主角 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 获取元素的大小等,由于需要确保获取到的值为最新的,浏览器都会先执行一次回流来保证值的正确。例如 offsetXXXclientXXXscrollXXX

重绘回流优化方案

知道了触发回流/重绘的原因,那么就能根据这些原因,制定相应的优化方案,如下。

  • 避免使用触发重绘回流的 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的更多相关文章

  1. 【前端优化之渲染优化】大屏android手机动画丢帧的背后

    前言 上周我与阿里的宇果有一次技术的交流,然后对天猫H5站点做了一些浅层次的分析,后面点时间基本天天都会有联系,中途聊了一些技术细节.聊了双方团队在干什么,最后聊到了前端优化.因为我本身参与了几次携程 ...

  2. 渲染优化 之fixed与返回顶部 以及开启GPU Hack

    fixed元素,常见网站右侧出现一个返回顶部的按钮,滚动的时候,会发现返回顶部这个区域在不停的进行重绘,而返回顶部是position:fixed定位的.这也解释了为什么fixed定位是最耗性能的属性之 ...

  3. Google Pagespeed,自动压缩优化JS/CSS/Image

    Google Pagespeed,自动压缩优化JS/CSS/Image 浏览: 发布日期:// 分类:技术分享 关键字: Nginx Appache Pagespeed 自动压缩优化JS/CSS/Im ...

  4. 如果要做优化,CSS提高性能的方法有哪些?

    一.前言 每一个网页都离不开css,但是很多人又认为,css主要是用来完成页面布局的,像一些细节或者优化,就不需要怎么考虑,实际上这种想法是不正确的 作为页面渲染和内容展现的重要环节,css影响着用户 ...

  5. CssStats – 分析和优化网站 CSS 代码的利器

    CssStats 是一个在线的 CSS 代码分析工具,你只需要输入网址或者直接 CSS 地址即可进行 CSS 代码的全方位分析,是前端开发人员和网页设计师分析网站 CSS 代码的利器,可以统计出 CS ...

  6. 转 cocos2d-x 优化(纹理渲染优化、资源缓存、内存优化)

    概述 包括以下5种优化:引擎底层优化.纹理优化.渲染优化.资源缓存.内存优化   引擎优化 2.0版本比1.0版本在算法上有所优化,效率更高.2.0版本使用OpenGl ES 2.0图形库,1.0版本 ...

  7. Normalize.css:优化重置CSS默认属性

    Normalize.css:优化重置CSS默认属性 官方网站:http://necolas.github.io/normalize.css/ 项目仓库:https://github.com/necol ...

  8. Unity渲染优化中文翻译(二)——CPU的优化策略

    紧接上一篇文章,继续渲染的优化问题,若有错误,请指出,让我也学习进步,谢谢. 如果游戏渲染问题来自CPU 概括的来说,CPU在一帧的渲染中的工作可以分为三个部分: . 决定谁需要被渲染 . 为GPU准 ...

  9. 剖析虚幻渲染体系(12)- 移动端专题Part 3(渲染优化)

    目录 12.6 移动端渲染优化 12.6.1 渲染管线优化 12.6.1.1 使用新特性 12.6.1.2 管线优化 12.6.1.3 带宽优化 12.6.2 资源优化 12.6.2.1 纹理优化 1 ...

随机推荐

  1. 1.3.8、通过RemoteAddr匹配

    server: port: 8080 spring: application: name: gateway cloud: gateway: routes: - id: guo-system4 uri: ...

  2. 6-x3 declare和typeset命令:设置变量属性

    declare 和 typeset 都是 Shell 内建命令,它们的用法相同,都用来设置变量的属性.不过 typeset 已经被弃用了,建议使用 declare 代替.declare 命令的用法如下 ...

  3. nginx 基本配置

    server { listen 80; server_name 域名; #access_log /var/log/nginx/admin.log; index index.html index.htm ...

  4. WPF教程五:附加依赖项属性

    附加依赖项属性是一个属性本来不属于对象自己,但是某些特定场景其他的对象要使用该对象在这种场景下的值.这个值只在这个场景下使用.基于这个需求设计出来的属性.这里主要涉及到一个解耦问题.最大的优势是在特定 ...

  5. Maven | 把jar包安装到本地仓库

    使用的场景 自己写的工具类想安装到本地 从Maven仓库中下载不下来的jar 使用的步骤 首先要保证自己的Maven配置全局环境变量,如果没有配置过maven全局变量,可以按照下面的步骤配置一下: 先 ...

  6. python 15篇 面向对象

    1.面向对象编程概念 面向对象是包含面向过程 面向过程编程 买车: 1.4s看车,买车 2.上保险 保险公司 3.交税 地税局 4.交管所 上牌面向对象编程 卖车处: 1.4s 2.保险 3.交税 4 ...

  7. WIN10 GMSSL编译

    从git上拉取GMSSL代码 从http://gmssl.org/上可以拉取,或者直接从git上https://github.com/guanzhi/GmSSL拉也行. 我是在git上下的,文件为gm ...

  8. Rust安装-运行第一个程序-hello_world

    Rust官网:https://rust-lang.org/ 安装 点击install,选择版本 选择相对应的版本进行下载 我这里下载的是windows系统,运行下载好的exe文件,根据需要选择选对应的 ...

  9. java并发编程基础——线程通信

    线程通信 当线程在系统内运行时,程序通常无法准确的控制线程的轮换执行,但我们可以通过一些机制来保障线程的协调运行 一.传统的线程通信 传统的线程通信主要是通过Object类提供的wait(),noti ...

  10. 国产深度学习框架mindspore-1.3.0 gpu版本无法进行源码编译

    官网地址: https://www.mindspore.cn/install 所有依赖环境 进行sudo make install 安装,最终报错: 错误记录信息: cat     /tmp/mind ...