前端二向箔08-浏览器CRP

342 阅读10分钟

08-浏览器CRP

上一篇

前端二向箔07-CSS选择器


在进行进一步的了解CSS排版之前, 有必要对浏览器如何构建网页有一个基本的认识. 本篇主要讲述CRP--Critical Render Path关键渲染路径中网页是如何被生成处理和展示的, 这对理解HTML的解析和CSS排版原理有一个基础的背景知识. 这也许是初学者不容易意识到的一些原理性的内容.

浏览器加载网页的流程

在进入CRP之前, 可以先从浏览器加载网页的例子入手.

面试经典老番: url到网页发生了什么?

一道典型的面试题: 你从输入URL到网页呈现过程中发生什么?

简要参考:

  1. DNS域名解析: 解析目标资源IP地址
  2. TCP连接: TCP三次握手
  3. 浏览器发起HTTP请求
  4. 服务器响应: 处理请求并返回HTTP报文
  5. 浏览接受数据, 解析资源, 渲染页面
  6. 断开连接: TCP四次挥手.

想要了解更多可以点击参考连接🔗经典面试题

其中浏览器的生产工作

把上述的例子类比成产品制造.

其中, ①~④: 域名解析和服务器的通信都属于网页生成的准备工作. 可以理解成购买原材料下订单交易的过程;

⑤解析资源渲染页面就是将入库的材料制作成产品的过程; 那么浏览器引擎就是一条流水线. 这条流水线有几个处理工艺:

  1. Parser: 解析HTML字符流
  2. DOM && CSSOM: 构造DOM树和CSSOM树
  3. Render: 将DOM和CSSOM结合生成渲染树
  4. Layout: 根据渲染树, 确定节点的大小\位置
  5. Paint & Composition: 分层处理节点图层, 进行光栅化绘制( 即为可见元素添加像素信息 ).

经过这条流水线, 就完成了网页这个"产品"的制作出厂, 而这条流水线就是CRP--关键渲染路径.

优化关键渲染路径是指优先显示与当前用户操作有关的内容。

通过对CRP的优化, 可以提高网页性能, 提高加载速度, 为构建高性能交互式应用打下基础.

Parser 解析(DOM解析)

浏览器获得HTTP响应报文后第一步就是读懂其中的内容, 报文都是一串串的字符流, 转化成浏览器认识的HTML需要经过如下过程:

image-20210312192136877

  • 转换: 将字节依据编码格式( 一般utf-8 )转化为字符
  • 令牌化: 将字符通过状态机的词法分析处理成HTML的标签, 即令牌化.
  • 词法分析: 进一步对获得的令牌分析HTML语法, 转换成相应的对象
  • DOM构建: 这是一个入栈的过程, 通过一些配对函数\算法处理, 输一个树的数据结构, 这个树就是DOM

上面这些步骤主要是根据response的响应头进行解析, 其中编码格式尤为重要, 确实相关信息将会导致字符流作为文本载入.

这里的解析只是一部分, 更重要的的DOM的解析和构建.

构建DOM其实就是浏览器生产一堆JS对象, 继承自结点类Node( 其他内置类有HTMLDivElement, HTMLScriptElement).

这些结点对象根据HTML形成了树状的结构(因为HTML本身就是设计成嵌套和继承的), 这个结构就是DOM .

但此处将DOM和CSSOM归纳在一起在于便于理解.

构建对象模型

1. DOM 文档对象模型

DOM是经过解析形成的数据结构,, 为后续的渲染提供素材.

读取html的内容并构造DOM树的过程, 也称之为DOM解析, 完成这个过程的程序被称之为DOM解析器

浏览器提供了DOM-API用于操作修改DOM上的元素, 即操作对象. 这是高层的API, 访问DOM元素的入口. 此外, 浏览器提供了DOMParser-API可以手工构造DOM树( parseFromString() ).

DOM的构建过程已在Parser解析部分提及, 此处不多解释.

这里需要关注一些DOM解析过程的一些行为特点:

  • DOM元素是渐进式增加到DOM树中的

    浏览器每次处理HTML标记都会完成一次构建行为.

  • 当解析遇到 script标签\link文件链接\图片标签 或者其他外部资源

    会在后台开始下载文件, 但不是在主线程main thread

  • DOM解析过程在主线程中进行, script是解析阻塞元素.

    如果javascript执行线程很忙, 将会停止DOM解析, 直到javascript执行完毕

  • 其他外部链接不会阻塞主线程, 而JS会:

    如果是内嵌脚本, 只有js执行完毕, 才会继续解析DOM

    如果是外部文件, 会从主线程此处位置开始下载, 并且只有下载完成并执行完毕后, 才会继续解析DOM.

  • DOM API可以让js动态操作元素, 这是Vue, React一类JS库的基础操作

  • 使用async属性

    JS可以异步加载(但不能异步执行!!), 不影响DOM的正常解析

  • 使用defer属性

    使得JS可以异步加载但不会执行, 只有DOM解析后才执行

  • 有些浏览器具有预解析的策略

    将一些HTML解析分配到其他线程以进行外部资源的预加载, 这个线程叫做预解析线程

    浏览器引擎不同不能保证预解析一定发生, 但是可以对link标签添加preload属性进行声明

2. CSSOM CSS对象模型

构建CSSOM和构建DOM类似, 在构建DOM的过程中, 浏览器从所有源头读取CSS信息, 包括

link-stylesheet( 发起请求 ), 内联属性, style标签, UA用户代理属性.

对应于每一个node结点css样式信息, 形成类似于DOM的结构, 即CSSOM. 解析流程也一样:

CSSOM Parser

但不同的是, 它是基于CSS的级联规则计算的样式继承信息, 和HTML的标签结构是不同的树状结构.

同样, CSSOM在这个过程中行为表现:

  • 当DOM和CSSOM同时构建完成, 最后结合形成渲染树

  • CSSOM不是渐进式的

    对于style和内联标签, 它会被解析CSSOM上, 然后继续解析DOM

    遇到link标签的外部css时, 他会继续解析DOM, 并在后台下载css文件, 且不会像处理DOM一样逐个添加到树上

    因为无法确定当前样式是否会被后续规则覆盖, 如果是渐进式的将可能导致重复渲染, 造成性能资源浪费

  • CSS是渲染阻塞的render-blocking, 而JS是解析阻塞parser-blocking

    虽然都会在页面展示前阻塞, 但是不同的两个东西

  • 当link发出请求获得外部资源, 渲染树就会暂停, 同时CRP也中断 渲染树需要CSSOM和DOM, 当CSSOM没有加载完全就进行渲染同样也会造成重复渲染.

  • 浏览器可以使用旧的CSSOM进行渲染,

    但是当link文件加载解析完毕更新CSSOM进一步更新渲染树, 可能会造成样式变化闪烁等FOUC问题

    渲染树会等待css加载完毕, 再更新, 并重新绘制在屏幕上, 这就是为什么将link-css放在head中的原因, 尽早加载完stylesheet

  • 由于js可能会操作之前的Dom节点和css样式,因此浏览器会维持html中css和js的顺序

    覆盖问题: 当js已经下载完毕且执行部分CSSOM-API, 如改变背景颜色, 但是还有没有下载完的css, 当css下载完, 这时又覆盖了背景颜色, 此时js操作就变得无效.

    为了避免这样的问题, H5规范中, CSSOM会在后面的js执行前先加载执行完毕, 所以CSS会阻塞后面js的执行。

渲染树 Render Tree

渲染树将得到的对象模型进行结合, 用于计算将要展示在页面上的元素的布局, 并将其结果发送到Layout和paint.

  • 通过结合DOM和CSSOM得到的树状结构

    render tree

    由于渲染树是最终将在屏幕上打印的内容的低级表示,因此它不会包含在像素矩阵中不包含任何区域的节点, 即:

    display为none的元素, script, link等会被忽略.

  • 由此易得: 除非DOM和CSSOM构建完成, 渲染不会进行, 即HTML和CSS是阻塞渲染的. 但要注意

    HTML( 即构建DOM), 实际上是阻塞解析.

    CSS( 构建CSSOM), 实际上才是阻塞渲染.

  • 浏览器会下载所有 CSS 资源,无论阻塞还是不阻塞

  • 除非渲染树完成绘制, 页面不会展示任何信息

布局Layout、绘制Paint以及合成Composite

得到了渲染树, 下一步浏览器就会以此进行布局绘制操作.

1. Layout布局

  • 渲染树完成就得到了布局信息

    效果上: 布局 = 重排 = 位置 + 大小

  • 发生条件: 页面加载\滚动\尺寸变化\DOM结构变化(通常是js操作DOM导致DOM变化, 需要重新计算页面)

2. Paint绘制(一般在CPU进行)

  • 元素会有交叠重合的部分, 也有富有变化的部分

    浏览器以绘制层layer来区分处理, 比如开发者设置z-index进行设置图层位置

  • 绘制层帮助浏览器在网页的整个生命周期中高效处理绘制操作

    例如调整窗口或者滚动时的处理和加载页面时的处理是不同的

  • 绘制层会分开渲染

    浏览器会把一个图层的绘制拆分成小的绘制指令,然后再把指令按照顺序组成一个待绘制列表

    截至到现在, 只是将要展示在屏幕上的东西准备好, 还未真正呈现在网页上.

    实际上绘制操作是由浏览器引擎中的合成线程来完成的

  • 合成

    • 没有必要绘制全部

      因为页面有时会很大, 浏览器不是一次性绘制完, 而是将视口viewpoint部分进行绘制.

    • 每一层都会被光栅化raster: 即进行元素的任何可见属性填充像素

      这个过程会将所有准备的好的绘制层分区块(减小重绘的开销), 并发送到GPU的线程池中进行栅格化, 之后展示在页面上

到这里, 我们完成了一个网页的展示.

回顾一下CRP:

  1. Parser: 解析HTML字符流
  2. DOM && CSSOM: 构造DOM树和CSSOM树
  3. Render: 将DOM和CSSOM结合生成渲染树
  4. Layout: 根据渲染树, 确定节点的大小\位置
  5. Paint & Composition: 分层处理节点图层, 进行光栅化绘制( 即为可见元素添加像素信息 ).

相关术语

1. 在开发者工具的性能performance中, 有以下术语

  • FP-First Paint: 第一次绘制, 如背景的第一个像素点开始
  • FCP-First Contentful Paint: 第一次内容绘制, 即文字和图片等第一次呈现在屏幕上
  • LCP-Largest Contentful Paint: 最大内容绘制, 绘制最大的内内容
  • DCL-DOMContentLoad: 触发document对象同时冒泡到window, 也相当于监听window的onload事件, 淡window的load事件要包括网页图片等完全加载完毕才触发

2. 构建过程中有以下术语:

  • 重排(或者回流)reflow: 更新了元素的几何属性, 触发一次完整的CRP过程.
  • 重绘: 更新元素的绘制属性, 布局阶段将不会被执行, 触发的是后续的Paint阶段.

引用

  1. 阻塞渲染的 CSS | Web | Google Developers. (2021). Retrieved 14 March 2021, from developers.google.com/web/fundame…
  2. How the browser renders a web page? — DOM, CSSOM and Rendering. (2020). Retrieved 14 March 2021, from medium.com/jspoint/how…