从输入URL到页面展示的工作流程,以及重排重绘概念及其优化

264 阅读5分钟

李兵老师《浏览器工作原理与实践》的学习笔记,插图均出自老师之手

导航流程——用户输入

  1. 用户输入url,地址栏根据规则给url加上协议,合成为完整url
  2. 触发当前页面beforeunload事件,在这里可以取消导航,如果取消浏览器不再执行后续工作,如果继续导航,标签页图标进入加载状态

导航流程——URL请求

  1. 浏览器进程通过进程间通信(IPC)把URL请求发到网络进程(以下是一次HTTP请求完整过程)

    1. 构建请求(请求行)
    2. 查找本地是否有该资源的缓存,若有直接返回
    3. DNS解析,准备好IP地址,再准备好端口号
    4. 等待TCP队列
    5. 建立TCP连接(三次握手)
    6. 发起HTTP请求、服务器处理请求、服务器响应请求
    7. 如果没有设置keep-alive,断开TCP连接(四次挥手)

    HTTP-.webp

  2. 网络进程收到响应之后,开始解析响应头

    1. 重定向: 如果是301或302,读取Location地址,发起新的HTTP请求,一切从头开始
    2. 响应数据类型处理: 根据Content-Type决定如何处理数据

导航流程——准备渲染进程

为页面创建渲染进程

  1. 跟打开它的页面协议和根域名相同,则与其共用渲染进程
  2. 否则创建新的渲染进程

导航流程——提交文档

  1. 浏览器收到网络进程响应头数据后,向渲染进程发起提交文档的消息
  2. 渲染进程收到后,和网络进程建立传输数据的管道
  3. 数据传输完之后,渲染进程返回确认提交的消息给浏览器进程
  4. 浏览器收到确认消息后,更新浏览器界面状态,包括安全状态、地址栏URL、前进后退的历史状态、更新web页面

渲染流程

渲染进程拿到了html,开始干活

08828b1544d3f08282c6a8d88227ab1c.png

  1. 构建DOM树:将html转换为DOM树

  2. 样式计算

    1. 把CSS文本转换为styleSheets
    2. 转换(标准化)样式表中的属性值,比如em变为px,black变为rgb(0,0,0)
    3. 计算出DOM节点的具体样式(根据CSS的继承和层叠规则),存到ComputedStyle中
  3. 布局阶段 Layout

    1. 创建布局树:只包含可见元素,遍历DOM树中的所有可见节点,加到布局树中
    2. 布局计算:计算每个元素的几何坐标位置,并将计算的信息保存回布局树中
  4. 分层 Layer:渲染引擎为特定节点生成专用的图层,没有独立图层的元素归属于父元素图层

    1. 拥有层叠上下文属性的元素被提升为单独一层
    2. 需要裁剪的地方会被创建为图层(overflow),若出现滚动条,滚动条也会被提升为层
  5. 图层绘制 Paint:图层的绘制分为很多小绘制指令,所有指令形成绘制列表

  6. 分块:渲染主线程将绘制列表提交至合成线程,将较大图层分为图块

  7. 栅格化操作:在光栅化线程池中,将视口附近的图块优先生成位图。

  8. 合成绘制显示 DrawQuad:所有图块都被光栅化,合成线程生成绘制图块的命令DrawQuad,把DrawQuad提交给浏览器进程,浏览器进程将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

相关概念(重排重绘合成)

重排

1dd7cb179fbc33e5d6cf1f8fb4908605.webp

  1. 更新了元素的几何属性(如宽、高、边距)
  2. 值得注意的是,读取offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight这些属性,或者调用getComputedStyle也会触发重排,因为这些都是要即时计算得到的。
  3. 触发重新布局,解析之后的一系列子阶段,更新完成的渲染流水线,开销最大

重绘

38e3f3246adda493b5b6471ee5d26062.webp

  1. 更新元素的绘制属性(元素的颜色、背景色、边框等);
  2. 布局阶段不会执行(无几何位置变换),直接进入绘制阶段。

合成

4309b97753a07f43ec11af76980ca70c.webp

  1. 直接进入合成阶段(例如CSS 的 transform 动画),这就是CSS动画比JS动画性能高的原因
  2. 直接执行合成阶段,开销最小

针对重排重绘的性能优化方法

  1. 使用 class 操作样式,而不是频繁操作 style

  2. 就算需要操作style,也要让对 dom 属性的读写要分离,现代chrome浏览器缓存了一个 flush 队列,把触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已“的时候,再将这些任务一口气出队

    let container = document.getElementById('container')
    container.style.width = '100px'
    console.log(container.clientWidth) // 读取clientWidth,就是上文提到的”不得已“
    container.style.height = '200px'
    
  3. 避免使用 table 布局

  4. 批量dom 操作,例如createDocumentFragment,批量创建dom之后再加到文档中。相应的还有两个方法,一是先用display: none把元素离线,操作完再恢复;二是直接通过字符串(html文本)的方式添加元素。这里面一定是字符串化性能最好。

  5. 防抖节流要频繁改变元素样式的操作

  6. 尽量使用CSS动画代替JS动画

  7. css的will-change

    CSS 属性 will-change 为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

    用好这个属性并不容易,两篇参考文章 mdn 某博文