引言

在开始介绍今天的主角 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. CentOS-Docker安装RabbitMQ集群(rabbitmq:3.7.16-management)

    准备工作 1.机器资源(分别安装docker环境) 建议机器配置: centos7.x 4G及以上 100GB及以上 2核及以上 192.168.1.101 192.168.1.102 192.168 ...

  2. linux 之管道命令与重定向

    一.Linux重定向 重定向能够实现Linux命令的输入输出与文件之间重定向,以及实现将多个命令组合起来实现更加强大的命令.这部分涉及到的比较多的命令主要有: 涉及到的比较多的命令主要有: cat:连 ...

  3. XCTF GAME

    首先这题有两种解法,一种是使用ida查看伪代码直接写exp跑出flag,另外一种是调试,因为最近在学调试,刚好用于实战上了. 一.查壳 二.32位文件拖入od动态调试 先找到game的主要函数,插件中 ...

  4. WPF使用Microsoft.VisualBasic创建单例模式引起的权限降低问题

    在进行WPF开发时,总是在找更加优雅去写单例模式的代码. 很多人都喜欢用Mutex,一个App.cs下很多的Mutex,我也喜欢用. 看完<WPF编程宝典>的第七章Applicaton类后 ...

  5. asp.net mvc请求流程

    收对应用程序的第一个请求 > 执行路由 > 创建 MVC 请求处理程序 > 创建控制器 > 执行控制器 > 调用操作 > 执行结果

  6. CTF-safer-than-rot13-writeup

    safer-than-rot13 题目信息 附件: cry100 XMVZGC RGC AMG RVMG HGFGMQYCD VT VWM BYNO, NSVWDS NSGO RAO XG UWFN ...

  7. C语言:键盘输入

    C语言有多个函数可以从键盘获得用户输入,它们分别是: scanf():和 printf() 类似,scanf() 可以输入多种类型的数据. getchar().getche().getch():这三个 ...

  8. C语言:清空缓冲区

    缓冲区的优点很明显,它加快了程序的运行速度,减少了硬件的读写次数,让整个计算机变得流畅起来:但是,缓冲区也带来了一些负面影响,经过前面几节的学习相信读者也见识到了.那么,该如何消除这些负面影响呢?思路 ...

  9. Java基础00-抽象类20

    1. 抽象类 1.1 抽象类概述 代码示例:没有{}大括号的方法就是一个没有方法体的方法,要把它定义成抽象方法,就要给它加一个abstract关键字,而类中有抽象方法,该类也必须是一个抽象类,所以给类 ...

  10. groff编写man页

    groff 是大多数 Unix 系统上所提供的流行的文本格式化工具 nroff/troff 的 GNU 版本.它一般用于编写手册页,即命令.编程接口等的在线文档.在本文中,我们将给你展示如何使用 gr ...