浏览器渲染机制和 Reflow(回流、重排)和 Repaint(重绘)

前言

在之前输入 URL 到按下 return 发生了什么throttle(节流)和 debounce(防抖)都有提到渲染,和渲染线程,但是他是如何运作的呢?

渲染过程

  1. 解析 HTML 之后会转为 DOM 树结构
  2. 解析 CSS 会生成 CSS Rule 树,类似 DOM 树
  3. 浏览器通过 DOM 树和 CSS Rule 树构造 Render 树 recalculate style
  4. 根据 Render 树分割多个图层
  5. 对图层的 DOM 计算样式
  6. 为每个 DOM 生成图形和位置
  7. 将节点绘制填充到图层中
  8. 图层给到 GPU
  9. 最终展示

PS:步骤 2 的地方,通过 DOM 树和 CSS Rule 树构造 Render 树,这里就是通过 CSS 选择器来决定 DOM 的样式,所以如果浏览器不知道这个节点准确对应的样式,就是通过.foo > .bar这种元素内的选择器,浏览器也需要通过递归的方式去查找,所以来讲要 DOM 尽量简单,而 CSS 尽量使用 ID 和 Class 的方式去选择。

而且我们大多数关心的是 Chrome 的指标,可以通过 Performance 查看

  1. Recalculate style(计算样式
  2. Layout(布局
  3. Update layer tree(更新图层树
  4. Paint(绘制
  5. Composite layers 合并图层

其中 Layout 过程中就是标题的 Reflow,Paint 就是 Repaint,如果细致控制这个过程,从而减少 Reflow 和 Repaint 就有助提高性能

Reflow

Reflow 如何理解呢?我们知道了这些 HTML CSS 代码最后都会变成图层,那么 Reflow 就是影响了这个图层的排版,比如我有一个 list,改变第一个 item 的时候影响到的就是下面所有 item, 如果改变的是最后一个 item,那么影响的范围就缩小了。同理如果我们能做到不改变,就不影响,下面有一些影响的属性列表

  • 盒子模型相关
    • width
    • height
    • padding
    • margin
    • display
    • border相关的
  • 定位属性和浮动
    • top
    • bottom
    • left
    • right
    • position
    • float
    • clear
  • 文字结构
    • text-align
    • overflow
    • font-weight
    • line-height
    • vertival-align
    • white-space
    • font-size

Repaint

Repaint 就很好理解了,就是所有不影响布局的属性,比如background等,下面也给出一些影响的属性

  • color
  • border-style
  • border-radius
  • visibility
  • text-decoration
  • background相关
  • outline相关
  • box-shadow

Reflow 和 Repaint 小结

理解了这两个之后,大概就知道什么时候会 Reflow 和 Repaint 的触发规则简单的说就是:

  • Reflow
    • DOM 的增删
    • DOM 大小变化,包括内容变化所影响的变化,还有就是无论是marginpadding还是bordertop等等
    • DOM 位置变化
    • resize 事件,浏览器窗口尺寸变化也会触发
  • Repaint
    • 几乎所有的 style 变化,不包含大小改变

这里可以看出,如果我只改变 style 变化,是不会触发 Reflow,只会触发 Repaint,但是如果一旦触发了 Reflow,那么必定会触发 Repaint,为了性能考虑,尽量不去影响布局。

table

这个是一个例外,table布局无论改变什么,都会影响整个table,尽量减少使用。

独立图层

既然有图层的概念,那么一定有多个图层,但是如何创建呢?

  • 最常见的独立图层就是position: fixed,我们常用于当app-headerapp-bar等等
  • video, iframe
  • flash
  • will-change这个属性就是告诉浏览器将会发生什么,请帮我设成独立图层
  • 重叠 DOM 的整个父 DOM
  • 3D 或者硬件加速的2D Canvas
  • 3Dtransform
  • 。。。等等详细可以去看无线性能优化:Composite

最直接的方法是will-change但是由于版本稍高,所以用transform: translateZ(0)

这里有个 🌰,掘金的main-header明明已经position: fixed,却还设置transform: translateZ(0),不知道出于什么目的。

查看图层

打开 Chrome 的 Layers 就可以看到,本博客的评论区使用了iframe,还有header-post区域

优化

如何可以减少 Reflow 和 Repaint 呢?羡慕有几个方法:

  • 减少次数或者范围
    • 预先设定img的大小,以免载入图片之后大小变化导致 Reflow
    • position: absolute脱离文档流减少影响,从而减少 Layout 影响范围
    • 预先定义 class,然后修改 dom class 而不是直接操控 css
    • 如果大量 css 操作,可以通过display或者visibility将 DOM 隐藏离线,然后统一修改
    • 不要使用table布局
    • DOM 的一些需要计算的属性不要循环内重复获取,从而刷新浏览器缓存,例如offsetHeight
      • 这个只是针对低版本浏览器
    • 不要直接定时改变位置大小做动画
  • 完全规避 Reflow 和 Repaint
    • 使用translate来替代topmargin位移
  • 减少影响范围
    • 对动画新建图层
  • GPU 加速
    • 使用 GPU 硬件加速,可以直接使用transform: translateZ(0)或者transform: translate3d(0, 0, 0)

transform 和 opacity

正常流程就是跟之前一样 Recalculate style => Layout => Update layer tree => Paint => Composite layers,但是如果使用了transformopacity,Chrome 会直接不执行 Layout 和 Paint,直接在 Composite layers 中处理。

谨慎独立图层和 GPU 硬件加速

如果滥用图层,很有可能计算时间会增长在 Composite layers 中处理,而不是提升性能了。
如果滥用的是 GPU 硬件加速,虽然他很牛皮,但是如果滥用的话,每一个图层都需要交给 GPU 计算,考虑 GPU 和 CPU 的带宽和内存,还有可能会变得更慢。

总结

此项优化大多数可能是针对 Layout 和 Paint 比较频繁,单次执行时间过长而作出的提升优化,而对于比较简单的页面,可能真的用不上,但是 Get 到这个知识之后,做动画和频繁变动 DOM 的情况,就需要思考一下了。

这里也算是完善了输入 URL 到按下 return 发生了什么浏览器渲染这个环节。

说起来好笑,掘金最近一直我在当 🌰 使用,当我们在讨论前端面试,其实我们在讨论什么?中提到的通过锚点实现点击直接进入 detail 页面评论区域没有做好,也是因为图片没有预先设定大小,导致效果不好,又影响了 Reflow,得不偿失。