浏览器渲染解密:揭秘网页背后的魔法世界

前言

在面试过程中,常常会遇到键入一个url到页面渲染出来的过程。要完整地全部说出来并不容易,这个过程实际上是相当复杂的,考察了面试者关于网络和浏览器相关知识。这个过程大致可以分为网络渲染两个部分。
在这篇文章,就带你探究浏览器在拿到HTML文档之后,如何将其渲染到页面上。

一:渲染任务

浏览器是多进程架构,每个进程都各司其职,在渲染进程工作的过程中,子线程也各司其职,(浏览器的多进程架构在之后的文章会说明)

  1. 网络线程接收到HTML文档后,会生成对应的渲染任务

  2. 然后进入提交文档阶段,把渲染任务提交给渲染主线程中

  3. 浏览器进程会更新浏览器界面状态,包括安全状态,地址栏的url,前进后退历史状态

在这之后就正式进入到了渲染阶段...

二:构建DOM树

渲染主线程拿到渲染任务之后就开始解析HTML(parse HTML),可以打开浏览器开发者工具中的performance工具查看主线程main任务,可以查看具体过程。

为什么需要构建dom树?

1.浏览器无法直接理解和使用HTML,所以需要将HTML转换成浏览器可以理解的树形结构

2.构建DOM树后,JavaScript可以通过DOM API访问和操作文档的结构和内容

3.CSS规则通常基于文档的结构来定义样式,构建DOM树允许浏览器了解文档的结构,并根据样式规则应用样式

可以在控制台查看document结构,通过这样的结构,可以使得我们在控制台通过js对想要操作的dom节点进行更改

三:构建CSSOM树

在解析HTML的时候,可能会遇到外部的css,js文件,这时候浏览器会开启一个预解析线程先下载外部的css,js文件。

当解析到script的地方会停止解析,会等待下载并执行完成js才会继续解析,因为js可能会更改dom树,这就是js引擎和渲染引擎是互斥的原因。(更具体的在之后的文章去描述,这里不展开说了)

浏览器也无法直接理解和使用css,因此也需要转换成浏览器所能理解的树形结构。

收集样式表

在转换之前需要把HTML中所有的css样式表收集起来

  • 外部样式表(link标签)
  • 内联样式表(HTML元素内定义)
  • 嵌入样式表(style标签)

解析样式表

  • 对每个找到的样式表进行解析,将其转化为浏览器能够理解树形数据结构
  • 解析过程包括将CSS代码解析为样式规则、选择器和属性
  • 处理选择器以确定其作用的HTML元素

构建CSSOM树

  • CSSOM树的节点对应样式规则,包含选择器和样式属性。
  • 构建的树形结构反映了样式表中规则的层次结构和继承关系。
  • 节点之间的关系基于样式规则的嵌套和继承关系。(具体计算是在样式计算阶段,不要搞混哦

可以在控制台中通过document.styleSheets查看转化后的cssom结构

  1. 收集样式表(外部样式表,内联样式表,嵌入样式表)
  2. 解析样式表。转化为浏览器能够理解树形数据结构。将CSS代码解析为样式规则、选择器和属性。处理选择器以确定其作用的HTML元素
  3. 构建CSSOM树。CSSOM树的节点对应样式规则,包含选择器和样式属性。构建的树形结构反映了样式表中规则的层次结构和继承关系。节点之间的关系基于样式规则的嵌套和继承关系

四:DOM树和CSSOM树结合

  1. 将构建好的CSSOM树与DOM树进行关联。这样,每个DOM元素都可以找到与之相关联的样式规则
  2. 关联的过程根据CSS规则的选择器匹配相应的DOM元素

五:样式计算

一旦DOM树和CSSOM树结合,浏览器可以计算每个元素的最终样式。这包括继承属性、层叠规则等。计算样式的过程为每个元素确定了最终的呈现样式。最终得到带有样式的DOM树

考虑继承属性

  • 某些样式属性是可以继承的,例如colorfont-family等。如果某个元素没有显式指定这些属性,浏览器会查找其父元素的样式,将继承的值应用到该元素上。

处理选择器的权重

  • 对于通过多个选择器定义的样式规则,计算选择器的权重以确定哪个规则的样式应该优先应用

解决层叠规则

  • 层叠规则指定了在权重相等的情况下如何处理样式的冲突。例如,通过!important声明的样式规则具有更高的优先级。
  • 考虑!important规则以及后面出现的规则优先的规则。

处理单位和计算值

  • 将样式中的相对单位(如em、rem、百分比等)转化为绝对单位(像素等)
  • 计算属性值,例如将margin和padding值相加得到最终的盒模型尺寸。(注意盒模型很重要,布局阶段会用到)

可以到element元素开发者工具下的Computed查看最终每个dom元素计算的样式

  1. 考虑继承属性:某些样式属性是可以继承的,例如colorfont-family
  2. 处理选择器的权重:对于通过多个选择器定义的样式规则,计算选择器的权重以确定哪个规则的样式应该优先应用
  3. 解决层叠规则:层叠规则指定了在权重相等的情况下如何处理样式的冲突
  4. 处理单位和计算值:将样式中的相对单位(如em、rem、百分比等)转化为绝对单位(像素等)

六:布局

布局(Layout)阶段,也称为回流(Reflow)阶段。

浏览器根据计算得到的样式和元素的盒模型信息来确定页面中各个元素的位置和尺寸。

确定盒模型位置

  • 确定每个元素在文档流中的位置,包括上下文流和浮动元素的影响。
  • 考虑浮动元素的位置,以及文档流中其他元素对其位置的影响。

创建布局树

  • 结合DOM树和计算样式后的dom树(ComputedStyle),构建布局树。布局树包含了需要进行布局(去除不需要布局的元素)的元素及其关系
  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中,而不可见的节点会被布局树忽略掉(注意哪些元素不会进入布局树?

递归布局树

  • 从布局树的根节点开始递归,计算每个元素的大小和位置。
  • 确定每个元素的边界框,考虑盒模型属性的影响。

计算并更新位置:

  • 计算元素的最终位置和大小,更新布局信息。
  • 如果计算样式或内容发生了变化,可能需要重新触发计算样式和布局。(注意什么会触发回流?)

布局之后就要进入绘制合成阶段了...

绘制阶段主要包括:分层绘制图层(生成绘制指令列表),(主要是渲染主线程完成)

合成阶段主要包括:栅格化合成和显示(主要是合成线程完成)


绘制阶段...

七:分层

在分层阶段,浏览器将页面内容分成多个独立的图层,这样可以更有效地处理布局、绘制和合成,提高整体的渲染性能

生成图层

  • 根据布局阶段的计算结果,决定哪些元素应该成为独立的图层。
  • 元素可能会被分为多个图层,以便更好地利用硬件加速和优化渲染性能。

在开发者工具中找到layers,打开就可以看到一个网页被分成了多个图层。例如chatgpt的网页分层结构

浏览器页面实际上被分成了很多图层,这些图层叠加后合成最终的画面

那么哪些元素会成为单独一个图层呢?

  • 具有一些可能影响渲染性能的属性的元素,例如拥有 3D 变换(transform)、透明度(opacity)、滤镜(filter)、遮罩(mask)等属性的元素。
  • 使用 CSS 属性 transform: translate3d(0, 0, 0);will-change: transform; 等方式触发硬件加速的元素。
  • <video><canvas> 元素通常被放入独立图层,以便更好地处理它们的内容,特别是在视频播放或 canvas 动画时。
  • 具有固定定位(position: fixed;)的元素通常会被放入单独图层,这有助于更有效地处理滚动和定位。

创建图层树

  • 将确定的图层组织成图层树,这个树结构反映了页面中元素的层级关系
  • 图层树的结构通常与DOM树和CSSOM树相似,但是是为了优化而构建的注意哦)。
  1. 生成图层:根据布局阶段的计算结果,决定哪些元素应该成为独立的图层。
  2. 创建图层树:将确定的图层组织成图层树,这个树结构反映了页面中元素的层级关系

八:绘制图层

一旦图层树被创建,浏览器就需要将每个图层的内容绘制到位图缓冲区,以准备最终的合成和显示

生成绘制列表

  • 渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

可以打开"开发者工具"的"Layers"标签,选择"document"层,点击'profiler'查看绘制列表

为图层创建位图缓冲区

  • 对于每个图层,浏览器会为其创建一个位图缓冲区。这个缓冲区通常是在内存中的,用于存储栅格化后的位图数据。(为后续栅格化做准备)

1.生成绘制指令列表:渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

2.为图层创建位图缓冲区:对于每个图层,浏览器会为其创建一个位图缓冲区。这个缓冲区通常是在内存中的,用于存储栅格化后的位图数据


合成阶段...(渲染主线程把绘制列表交给合成线程进行处理)

九:栅格化

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的

分割图层

  • 图层内部可能包含了多个元素,合成线程需要将图层分割成可以独立处理的区域,每个区域称为一个图块(Tile)。
  • 这有助于并行处理和提高栅格化的效率。

栅格化图块

  • 对于每个图块,合成线程执行栅格化操作,将图块中的矢量图形转换为位图(像素点)。
  • 这包括对文本、形状、图像等元素进行具体的像素计算
  • 栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中

1.分割图层: 把图层分割成 2. 栅格化图块:将图块中的矢量图形转换为位图(像素点)

十:合成和显示

在这个阶段,位图图层经过栅格化生成的位图将被合并,以形成最终的屏幕图像

图层合成

  • 合成阶段负责将所有图层的内容进行合并。这包括对每个图层的透明度、混合模式等属性的考虑,确保合成的结果符合预期
  • 合成阶段将不同图层的位图数据合并,经过透明度、混合等处理后形成最终的合成图像

图像输出到屏幕

  • 合成线程将最终的合成图像输出到屏幕上。这包括将图像的位图数据传递给显示设备。
  • 显示设备(通常是显示器)读取位图缓冲区中的像素数据,然后根据这些数据点亮相应的像素,从而在屏幕上呈现最终的渲染结果。

由于直接操作像素可能会涉及大量计算,因此硬件加速的使用(如GPU)能够显著提高图像输出的效率

  1. 图层合成:将所有图层的内容进行合并
  2. 图像输出到屏幕: 合成线程将最终的合成图像输出到屏幕上,显示设备读取位图缓冲区中的像素数据,然后根据这些数据点亮相应的像素,从而在屏幕上呈现最终的渲染结果

总结

最后做一个总结把,再把这些东西重新简单串一遍

解析HTML ----渲染主线程

    1. 生成渲染任务。网络线程获取到HTML文档,生成渲染任务交给任务队列,渲染主线程从任务队列中读取选渲染任务
    1. 构建DOM树。把HTML转换成浏览器能够理解的树形数据结构
    1. 构建CSSOM树。把CSS转换成浏览器能够理解的树形结构
    1. DOM树和CSSOM树结合。
    1. 样式计算。得到一棵带有样式的DOM树。
    1. 布局。dom树和样式dom树结合去除一些不加入布局的元素,生成布局树

绘制(生成绘制指令列表)----渲染主线程

    1. 分层。浏览器把页面分成多个图层,生成图层树。
    1. 绘制图层。浏览器为每一个图层生成绘制指令列表,并把绘制指令列表交给合成线程进行真正的绘制

合成和显示 ---合成线程

    1. 栅格化。将每图层分块,再利用GPU将块变成位图。根据指令对文本、形状、图像等元素进行具体的像素计算
    1. 合成和显示。合成线程会根据绘制指令,对不同的图层进行合成。最终的合成图像将被传递到显示设备,以在用户屏幕上呈现。

看了很多篇关于浏览器渲染的文章,有的文章并没有把这些东西像一条线全部串起来,就会觉得很杂乱。 还有一些优秀的文章之间也会出现内容冲突,属于这个阶段的内容,在另一篇文章中属于另一个阶段。

我也是把自己理解的过程分享出来,如果错误,希望指正,我会及时更改!

参考:

浏览器渲染原理

李兵老师的渲染流程

相关推荐
代码搬运媛2 分钟前
【react实战】如何实现监听窗口大小变化
前端·javascript·react.js
小桥风满袖4 分钟前
Three.js-硬要自学系列30之专项学习环境光
前端·css·three.js
Luckyfif7 分钟前
🤯由 性能指标 散发开来的 Performance API 被问爆了呀
前端·面试·性能优化
咸虾米10 分钟前
在uniCloud云对象内使用unipay的微信退款出现错误“uniPayCo.refund Error: token校验未通过”的解决方案
前端·后端
前端Hardy16 分钟前
HTML&CSS:产品卡片动画效果
前端·javascript
货拉拉技术22 分钟前
货拉拉开源:鸿蒙路由 TheRouter
android·前端·harmonyos
中杯可乐多加冰24 分钟前
工业4.0数字孪生新引擎:星图云开发者平台全景评测
前端·低代码·掘金·金石计划
云边小卖铺.30 分钟前
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
前端·javascript·vue.js
我是若尘32 分钟前
前端处理大量并发请求的实用技巧
前端