Skip to content

目标

  • 理解浏览器重绘与回流的机制
  • 对于一些经典案例进行分析
  • 重绘与回流的案例实战

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的过程

  1. 获取DOM后分隔为多个图层
  2. 对每个图层的节点计算样式结果 (Recalculate style -- 样式重计算)
  3. 为每个节点生成图形和位置 (Layout -- 回流和重布局)
  4. 将每个节点绘制填充到图层位图中 (Paint Setup 和 Paint -- 重绘)
  5. 图层作为纹理上传至GPU
  6. 符合多个图层到页面上生成最终屏幕图像 (Composite Layers -- 图层重组

将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流影响只会在这个图层中。

Chrome 创建图层的条件

  1. 3D或透视变换的CSS属性 (perspective transform)
  2. 使用加速视频解码 <video> 节点
  3. 拥有3D (WebGL) 上下文或加速的2D上下文的 <canvas> 节点
  4. 混合插件 如Flash
  5. 对自己的opacity 做CSS动画或使用一个动画webkit变换的元素
  6. 拥有加速CSS过滤器的元素
  7. 元素有一个包含复合层的后代节点 (一个元素拥有一个子元素,该子元素在自己的层里)
  8. 元素有一个 z-index 较低且包含一个复合层的兄弟元素 (该元素在复合层上渲染)

CSS3属性 will-change

实战优化点

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结点的属性值放在一个循环里当成循环里的变量

js
// 假设有 <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的值(与浏览器机制相关) 缓存区不断计算该高度可能导致最终结果有误差

所以,在循环中尽量别 实时获取 会影响页面 布局的相关属性~~ 这样会在一定程度上影响浏览器绘制

6. 不要使用table布局 (小改动也会造成整个table的重布局)

7. 动画实现的速度的选择

8. 对于动画新建图层

9. 合理启用GPU硬件加速

共 20 个模块,1301 篇 Markdown 文档。