引言

在开始介绍今天的主角 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. linux安装subversion

    原文: https://www.cnblogs.com/liuxianan/p/linux_install_svn_server.html 安装 使用yum安装非常简单: yum install su ...

  2. java基础---泛型机制

    从java5 开始增加泛型机制,用于明确集合中可以放入的元素类型,只在编译时期有效,运行时不区分是什么类型. 格式:<数据类型> 泛型的本质是参数化类型,让数据类型作为参数传递,E相当于形 ...

  3. C语言:#error命令,阻止程序编译

    #error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下: #error error_message 例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样 ...

  4. java集合(3)-Java8新增的Stream操作集合

    Java8新增了Stream,IntStream,LongStream,DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素.上面的4个接口中,Stream是一个通用的 ...

  5. 跟我一起学Go系列:gRPC 全局数据传输和超时处理

    gRPC 在多个 GoRoutine 之间传递数据使用的是 Go SDK 提供的 Context 包.关于 Context 的使用可以看我之前的一篇文章:Context 使用. 但是 Context ...

  6. 单细胞分析实录(17): 非负矩阵分解(NMF)代码演示

    本次演示使用的数据来自2017年发表于Cell的头颈鳞癌单细胞文章:Single-Cell Transcriptomic Analysis of Primary and Metastatic Tumo ...

  7. React组件三大属性之state

    React组件三大属性之state 组件被称为"状态机", 页面的显示是根据组件的state属性的数据来显示 理解1) state是组件对象最重要的属性, 值是对象(可以包含多个数 ...

  8. 数组去重汇总—v客学院技术分享

    上周基础班结束了数组的学习内容,这几天有时间整理了下几种比较常用的数组去重的方法供大家查阅!!!!! 话不多说,直接贴代码吧~~~~~~~ 欢迎大家指正,共同学习,一同进步!!! (php开发,web ...

  9. Requests方法 -- 参数化

    import requests#禁用安全请求警告from requests.packages.urllib3.exceptions import InsecureRequestWarningreque ...

  10. NestJS WebSocket 开始使用

    使用NestJs提供WebSocket服务. 本文会在新建项目的基础上增加2个类 Gateway 实现业务逻辑的地方 WebSocketAdapter WebSocket适配器 新建项目 新建一个项目 ...