浏览器的渲染过程

107 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

  1. 渲染DOM树:解析HTML文件,构建DOM树,同时浏览器主进程负责下载CSS文件
  2. 构建CSSOM树:CSS文件下载完成,解析CSS文件形成CSSOM Tree
  3. 生成渲染树:CSSOM + DOM => 渲染树(RenderTree)
  4. 布局(Layout):RenderObject 根据盒模型确定元素的尺寸,位置
  5. 绘制(Painting):绘制页面像素信息
  6. 显示(Display):浏览器主进程将默认的图层和复合图层交给 GPU 进程,GPU 进程再将各个图层合成(composite),最后显示出页面

一、DOM树的构建过程

Bytes => Characters => Tokens => Nodes => DOM

将二进制转换为文本 => 再用文本提取出内部的Token => 把 Token 转为相应的Node节点 => 最后生成DOM树

  1. Bytes:3C 64 6F 64 79 3E 48 65 6C ...
  2. Characters:<html><head></head><body><p>Hello<span>.....
  3. Tokens:StartTag:htmlStartTag:headEndTag:head...
  4. Nodes:htmlheadmetabodyp...
  5. DOM:最后生成DOM

二、CSSOM

3.png

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"
  • offsetLeftoffsetTop 的值放在临时变量中,没有进行任何的操作,所以只会重排一次
    • 连续读取的过程中,没有发生布局的变化
    • 这里div的位置和大小信息都没有被修改过

4、离线改变dom

注:重排是排列可见元素

隐藏要操作的dom

  • 在要操作 dom 之前,通过 display 隐藏 dom,当操作完成之后,才将元素的 display 属性设为可见。因为不可见的元素不会触发重排和重绘。
dom.display = "none"
// 修改dom样式操作
dom.display = "block"
  • 通过使用 DocumentFragment 创建一个 dom 碎片,在它上面批量操作 don,操作完成之后,再添加到文档中,这样只会触发一次重排

  • 复杂节点,在副本上工作,然后替换它

5、优化动画

  • 可以把动画效果应用到 position 属性为 absolutefixed 的元素上,这样对其他元素影响较小
  • 动画效果还应牺牲一些平滑,来换取速度
  • GPU 硬件加速:指应用 GPU 的图形性能,把浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。GPU 加速通常包括以下几个部分:Canvas2D,布局合成,CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)