目标
- 理解浏览器重绘与回流的机制
- 对于一些经典案例进行分析
- 重绘与回流的案例实战
CSS能让JavaScript变慢?
因为CSS放在head中,加载CSS也会阻塞浏览器资源加载
一个线程 => UI渲染 一个线程 => JS引擎
频繁触发重绘与回流,会导致UI频繁渲染,从而阻塞JS执行,最终导致JS变慢
回流与重绘
回流
当render tree中的一部分或全部因为元素的规模尺寸、布局、隐藏等改变而需要重新构建。 这就称为回流 Reflow
当页面布局和几何属性改变时就需要回流
回流涉及到页面布局 大小变化等
重绘
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不影响布局的,比如backgrond-color 则就称为重绘 Repaint
页面发生(任何)变化,都会发生重绘
回流必将引起重绘,而重绘不一定引起回流
触发页面重布局的属性
- 盒子模型相关属性会触发重布局
display padding margin border border-width width height min-height
- 定位属性及浮动也会触发重布局
clear float position top bottom left right
- 改变节点内部文字结构也会触发重布局
overflow overflow-y vertical-align line-height font-size font-family font-weight text-align white-space
只触发重绘的属性
visibility color box-shadow border-style border-radius text-decoration background background-image background-position background-repeat background-size outline outline-color outline-style outline-width
避免使用触发回流、重绘的CSS属性将回流、重绘的影响范围限制在单独的图层之内
新建DOM的过程
- 获取DOM后分隔为多个图层
- 对每个图层的节点计算样式结果 (Recalculate style -- 样式重计算)
- 为每个节点生成图形和位置 (Layout -- 回流和重布局)
- 将每个节点绘制填充到图层位图中 (Paint Setup 和 Paint -- 重绘)
- 图层作为纹理上传至GPU
- 符合多个图层到页面上生成最终屏幕图像 (Composite Layers -- 图层重组
将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流影响只会在这个图层中。
Chrome 创建图层的条件
- 3D或透视变换的CSS属性 (perspective transform)
- 使用加速视频解码
<video>节点 - 拥有3D (WebGL) 上下文或加速的2D上下文的
<canvas>节点 - 混合插件 如Flash
- 对自己的opacity 做CSS动画或使用一个动画webkit变换的元素
- 拥有加速CSS过滤器的元素
- 元素有一个包含复合层的后代节点 (一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个
z-index较低且包含一个复合层的兄弟元素 (该元素在复合层上渲染)
实战优化点
1. 用 translate 替代 top 改变
因为top会触发回流,但是translate不会
使用场景: 浮窗、弹窗及fixed广告窗等
2. 用opacity 替代 visibility
visibility 只触发重绘, 不会触发回流 opacity 即不会触发重绘,也不会触发回流
但会 opacity会影响其子节点 影响兄弟节点的repaint 所以需要把需要改的节点单独创建图层来修改: transform: translateZ(0)transform: translate3d(0,0,0)
3. 不要逐条修改DOM样式,预先定义好class,然后修改DOM的className
减少回流和重绘的触发,将多次改动减小为一次
4. 把DOM离线后修改,如:先把DOM的display:none(一次Reflow)然后修改100次再显示出来
5. 不要把DOM结点的属性值放在一个循环里当成循环里的变量
// 假设有 <div id="rect"></div>
var doms = [] // 通过选择器选择出了一个dom元素的数组
var domsTop = []
// 根据当前页面的可视区域高度,去计算这个dom元素的位置
for (var i = 0; i < 100; i++) {
var clientHeight = document.body.clientHeight // 获取可是区域高度
domsTop.push(clientHeight + i * 100)
}这样写最大的问题在于,JS脚本运行时,每次循环进行都会去计算文档中clientHeight的值(与浏览器机制相关) 缓存区不断计算该高度可能导致最终结果有误差
所以,在循环中尽量别 实时获取 会影响页面 布局的相关属性~~ 这样会在一定程度上影响浏览器绘制
