05-4 回流/重绘+分层合成机制
配合博文一起看:05 渲染流程:HTML、CSS和JavaScript是如何变成页面的
参考另一篇博文:回流/重绘
帧 + 帧率
-
显示器怎么显示图像?
每个显示器都有自己的刷新频率,一般是60HZ(每秒更新60张图片 – 来源于显卡中的 前缓冲区),将读取的图像显示到显示器上。
-
显卡的作用?
显卡的作用是 合成新的图像,并将图像保存到 后缓冲区 – 一旦合成的图像写到后缓冲区,系统就让前/后缓冲区互换 –> 为了保证显示器能够读取到最新显卡合成的图像
-
动画效果怎么实现?
在滚动鼠标或者缩放页面时,渲染引擎 会通过 渲染流水线 生成新的图片,并发送到显卡的后缓冲区。【如果要保持动画流畅,渲染引擎需要每秒更新60张图片到显卡的后缓冲区】
-
帧?
把渲染流水线生成的每一幅图片为一帧
-
帧率?
把渲染流水线每秒更新了多少帧称为帧率
举例:滚动过程中1秒更新了60帧,那么帧率就是 60HZ/60FPS
渲染引擎为了不卡顿做了什么? - 分层和合成机制
如何生成一幅图像
关于其中任意一帧的生成方式,有 重排、重绘和合成 三种方式,每种方式的渲染路径是不同的。
看个例子 - 点击按钮,一个盒子移动位置,每一帧要进行的处理:
可以在 chrome
的 performance
里面看到事件的循环
每一个循环拆解如下图所示:
Schedule Style Recalculation
(样式计算) -> Recalculate Style
(样式计算) -> Update Layer Tree
(回流/重排) -> Paint
(重绘) -> Composite Layers
(渲染层合并)
-
1、JS 实现动画效果
JavaScript 实现动画效果,DOM元素操作等。
-
2、Style: 样式计算 -
Schedule Style Recalculation
、Recalculate Style
确定每个 DOM 元素应该应用什么 CSS 规则
-
3、Layout: 回流/重排 reflow -
Update Layer Tree
-
定义:根据
CSSOM
+DOM
计算每个 DOM 元素在最终屏幕上显示的大小和位置 - 即 计算布局 –> 整个渲染流水线(从 html 开始递归往下计算尺寸和位置开始)的每个阶段都要执行一遍; -
触发事件
页面 初始渲染,这是开销最大的一次重排;
添加/删除 可见的DOM元素;
改变元素 位置 ;
改变元素 尺寸,比如边距、填充、边框、宽度和高度等;
改变元素 内容,比如文字数量,图片大小等;
改变元素 字体大小;
改变浏览器 窗口尺寸,比如 resize 事件发生时;
激活CSS伪类(例如:
:hover
);设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow ;
查询某些属性或调用某些计算方法:`offsetTop`, `offsetLeft`, `offsetWidth`, `offsetHeight`;`scrollTop`/Left/Width/Height;`clientTop`/Left/Width/Height;`width`,`height`;调用了`getComputedStyle()`, 或者 IE的 `currentStyle()` 等,原理是一样的,都为求一个“即时性”和“准确性”。
-
-
4、Paint: 重绘 rePaint -
Paint
- 定义:元素的外观发生改变,但没有改变布局 —> 依然需要计算绘制信息(样式)
- 触发事件:改变某个元素的 背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性 时,屏幕的一部分要重画,但是元素的几何尺寸没有变
-
5、Composite: 渲染层合并:
Composite Layers
在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。
关于 display:none
和 visibility:hidden
:
display:none
指的是元素完全不陈列出来,不占据空间,涉及到了DOM结构,故产生 回流+重绘visibility:hidden
指的是元素不可见但存在,保留空间,不影响结构,故只产生 重绘
减少重排次数的几种方法:
- 1、样式集中改变 - 更改类名 而不是修改样式
- 2、分离读写操作 - 浏览器渲染机制: 重排/重绘操作被触发后会被放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器才会 *批量执行 *这些操作
- 3、将 DOM 离线,修改完样式后再放出来
display:none
,只会触发 隐藏+显示 两次重排- 创建新的 DOM 节点,操作完之后再添加到页面
- 复制节点,操作完之后替换原节点
- 4、脱离文档流(
fixed
/absolute
) - 利用分层,只有当前层需要重排,代价开销小 - 5、优化动画 - 利用 GPU 加速:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。使用css3硬件加速,可以让transform、opacity、filters、will-change 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能
分层和合成流程
如果没有分层 –> 从布局树直接生成目标图片的话,每次很小的变化都会重排/重绘;
分层 –> 将素材分解为多个图层的操作;
合成 –> 最后将这些图层合并到一起的操作;
-
分层、合成流程
在 Chrome 的渲染流水线中,生成 布局树 之后
–> 开始分层:渲染引擎会根据布局树的特点将其转换为 层树(Layer Tree)(层树是渲染流水线后续流程的基础结构)
–> 层树中的 每个节点都对应着一个图层(下一步的绘制阶段就依赖于层树中的节点)
–> 绘制阶段:将绘制指令组合成一个 绘制列表
–> 光栅阶段:按照绘制列表中的指令生成图片,每一个图层都对应一张图片
–> 合成线程 将这些图片合成为“一张”图片,并最终将生成的图片发送到 后缓冲区
合成操作是在 合成线程 上完成的,这也就意味着在执行合成操作时,是 不会影响到主线程执行 的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因
分块
合成线程 会将每个图层 分割为大小固定的图块,然后优先绘制靠近 视口 的图块,这样就可以大大加速页面的显示速度。
注意:还有一个优化点,首次合成图块的时候用一个低分辨率的图片显示,然后合成器继续绘制网页,完成后再替换低分辨率内容。
写代码的时候能做什么优化?
如果需要对某些元素做【几何形状变换、透明度变换或者一些缩放操作】,如果使用 JS 来实现这些效果,会涉及到整个渲染流水线。
- 优化方案:利用下面这个样式的添加,渲染引擎会将该元素 单独实现一帧(不能随意增加,因为从渲染引擎准备一个独立层开始,后面的每一个阶段都会多一个层结构,都会占用额外的内存),等到这些变换发生时,渲染引擎会通过 合成线程 直接处理,就不会涉及到主线程 –> 这也就是为什么CSS动画比JS动画高效
.box { will-change: opacity、transform、top、left、bottom、right; }
使用 transform
或者 opacity
来实现动画效果 - 不会触发 layout 和 paint, 这样只需要做合成层的合并就好了
对比一下 height: 200px
-> height:100px
和 transform:scale(.5)
的差异