浏览器渲染机制和 Reflow(回流、重排)和 Repaint(重绘)
前言
在之前输入 URL 到按下 return 发生了什么和throttle(节流)和 debounce(防抖)都有提到渲染,和渲染线程,但是他是如何运作的呢?
渲染过程
- 解析 HTML 之后会转为 DOM 树结构
- 解析 CSS 会生成 CSS Rule 树,类似 DOM 树
- 浏览器通过 DOM 树和 CSS Rule 树构造 Render 树 recalculate style
- 根据 Render 树分割多个图层
- 对图层的 DOM 计算样式
- 为每个 DOM 生成图形和位置
- 将节点绘制填充到图层中
- 图层给到 GPU
- 最终展示
PS:步骤 2 的地方,通过 DOM 树和 CSS Rule 树构造 Render 树,这里就是通过 CSS 选择器来决定 DOM 的样式,所以如果浏览器不知道这个节点准确对应的样式,就是通过
.foo > .bar
这种元素内的选择器,浏览器也需要通过递归的方式去查找,所以来讲要 DOM 尽量简单,而 CSS 尽量使用 ID 和 Class 的方式去选择。
而且我们大多数关心的是 Chrome 的指标,可以通过 Performance 查看
- Recalculate style(计算样式
- Layout(布局
- Update layer tree(更新图层树
- Paint(绘制
- 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 大小变化,包括内容变化所影响的变化,还有就是无论是
margin
,padding
还是border
,top
等等 - DOM 位置变化
- resize 事件,浏览器窗口尺寸变化也会触发
- Repaint
- 几乎所有的 style 变化,不包含大小改变
这里可以看出,如果我只改变 style 变化,是不会触发 Reflow,只会触发 Repaint,但是如果一旦触发了 Reflow,那么必定会触发 Repaint,为了性能考虑,尽量不去影响布局。
table
这个是一个例外,table
布局无论改变什么,都会影响整个table
,尽量减少使用。
独立图层
既然有图层的概念,那么一定有多个图层,但是如何创建呢?
- 最常见的独立图层就是
position: fixed
,我们常用于当app-header
和app-bar
等等 video
,iframe
等flash
will-change
这个属性就是告诉浏览器将会发生什么,请帮我设成独立图层- 重叠 DOM 的整个父 DOM
- 3D 或者硬件加速的
2D Canvas
- 3D
transform
- 。。。等等详细可以去看无线性能优化: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
来替代top
和margin
位移
- 使用
- 减少影响范围
- 对动画新建图层
- GPU 加速
- 使用 GPU 硬件加速,可以直接使用
transform: translateZ(0)
或者transform: translate3d(0, 0, 0)
- 使用 GPU 硬件加速,可以直接使用
transform 和 opacity
正常流程就是跟之前一样 Recalculate style => Layout => Update layer tree => Paint => Composite layers,但是如果使用了transform
或opacity
,Chrome 会直接不执行 Layout 和 Paint,直接在 Composite layers 中处理。
谨慎独立图层和 GPU 硬件加速
如果滥用图层,很有可能计算时间会增长在 Composite layers 中处理,而不是提升性能了。
如果滥用的是 GPU 硬件加速,虽然他很牛皮,但是如果滥用的话,每一个图层都需要交给 GPU 计算,考虑 GPU 和 CPU 的带宽和内存,还有可能会变得更慢。
总结
此项优化大多数可能是针对 Layout 和 Paint 比较频繁,单次执行时间过长而作出的提升优化,而对于比较简单的页面,可能真的用不上,但是 Get 到这个知识之后,做动画和频繁变动 DOM 的情况,就需要思考一下了。
这里也算是完善了输入 URL 到按下 return 发生了什么浏览器渲染这个环节。
说起来好笑,掘金最近一直我在当 🌰 使用,当我们在讨论前端面试,其实我们在讨论什么?中提到的通过锚点实现点击直接进入 detail 页面评论区域没有做好,也是因为图片没有预先设定大小,导致效果不好,又影响了 Reflow,得不偿失。