持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
- 渲染DOM树:解析HTML文件,构建DOM树,同时浏览器主进程负责下载CSS文件
- 构建CSSOM树:CSS文件下载完成,解析CSS文件形成CSSOM Tree
- 生成渲染树:CSSOM + DOM => 渲染树(RenderTree)
- 布局(Layout):RenderObject 根据盒模型确定元素的尺寸,位置
- 绘制(Painting):绘制页面像素信息
- 显示(Display):浏览器主进程将默认的图层和复合图层交给 GPU 进程,GPU 进程再将各个图层合成(composite),最后显示出页面
一、DOM树的构建过程
Bytes => Characters => Tokens => Nodes => DOM
将二进制转换为文本 => 再用文本提取出内部的Token => 把 Token 转为相应的Node节点 => 最后生成DOM树
- Bytes:
3C 64 6F 64 79 3E 48 65 6C ... - Characters:
<html><head></head><body><p>Hello<span>..... - Tokens:
StartTag:html、StartTag:head、EndTag:head... - Nodes:
html、head、meta、body、p... - DOM:最后生成DOM
二、CSSOM
CSSOM会根据层级样式的继承关系,将对应的样式继承下来。例如上图中 body 上的 font-size: 16px。
这里只有样式,没有标签。比如上图的 p,在 body 下,它只知道,如果将来有 p 标签在 body 下,就是箭头所指的样式。
三、RenderTree
将DOM树和CSS树两者进行结合。骨架从 DOM 中来,骨架中对应标签的样式从CSSOM从获取后实现进来。
接下来进行布局和绘制就完成了
四、重绘和重排
从上面的顺序中,可以知道先进行布局(Layout),再进行绘制(Painting)
并且我们知道在**布局(Layout)**的过程中确定了元素的位置和尺寸。
那么我们就可以知道:重排的性能消耗一定大于重绘,如果修改了元素的位置或尺寸,那么就会触发重排
因为重排后一定会再次重绘。
在页面被用户访问时,DOM和CSS都有可能发生变化。一旦发生变化就需要重新绘制甚至重新布局
【重绘】repaint
- 字体颜色改变
- 背景色改变
- 边框颜色改变
【重排】reflow
也叫回流。元素位置或大小改变,一旦发生重排肯定后续的重绘也会进行一次。
- 添加或删除可见DOM元素
- 元素位置发生变化
- 元素尺寸,位置发生变化
- 页面渲染初始化
- 浏览器尺寸改变
- js获取元素计算后的属性:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollWidth、clientTop 等(因为要获取实际的值,所以会强制重排)
优化思路
- 能重绘尽量不重排
- 能少排不多排
- 能小区域不大区域
- 避免无效重排
- 利用GPU资源(transform)
1、读写分离
div.style.left = '10px'
div.style.top = '10px'
div.style.width = '20px'
div.style.height = '20px'
console.log(div.offsetLeft)
console.log(div.offsetTop)
console.log(div.offsetWidth)
console.log(div.offsetHeight)
由于属性读取会强制重排后再读取,所以尽量避免读一个属性写一个属性的操作。一起写完了之后再一起读
2、样式集中改变
// bad
let left = 10
let top = 10
el.style.left = left + "px"
el.style.top = top + "px"
// good
el.className += " classname"
// good
el.style.cssText += `; left: "${left}px"; top: "${top}px";`
3、缓存布局信息
// bad 强制刷新,触发两次重排
div.style.left = div.offsetLeft + 1 + "px"
div.style.top = div.offsetTop + 1 + "px"
- 获取
offsetLeft时就会强制进行一次重排- 这时候修改了div的位置信息
- 获取
offsetTop时又会强制进行一次重排,因为元素的位置信息之前被修改过了 - 设置值的时候不会强制重排,会进行懒重排,也就是到后面一次性重排
// good 缓存布局信息,相当于读写分离
let curLeft = div.offsetLeft
let curTop = div.offsetTop
div.style.left = curLeft + 1 + "px"
div.style.top = curTop + 1 + "px"
- 将
offsetLeft和offsetTop的值放在临时变量中,没有进行任何写的操作,所以只会重排一次- 连续读取的过程中,没有发生布局的变化
- 这里div的位置和大小信息都没有被修改过
4、离线改变dom
注:重排是排列可见元素
隐藏要操作的dom
- 在要操作 dom 之前,通过 display 隐藏 dom,当操作完成之后,才将元素的 display 属性设为可见。因为不可见的元素不会触发重排和重绘。
dom.display = "none"
// 修改dom样式操作
dom.display = "block"
-
通过使用
DocumentFragment创建一个 dom 碎片,在它上面批量操作 don,操作完成之后,再添加到文档中,这样只会触发一次重排 -
复杂节点,在副本上工作,然后替换它
5、优化动画
- 可以把动画效果应用到
position属性为absolute或fixed的元素上,这样对其他元素影响较小 - 动画效果还应牺牲一些平滑,来换取速度
- GPU 硬件加速:指应用 GPU 的图形性能,把浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。GPU 加速通常包括以下几个部分:Canvas2D,布局合成,CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)