前端进阶系列之《浏览器渲染原理》

想搞懂浏览器是怎么变魔术,把代码变成眼前漂亮页面的吗?🎩✨ 这次我们就以 www.baidu.com/ 为例,一起揭开这场视觉魔术的幕后秘密!从前端代码到流畅的用户界面,每一步都藏着超有意思的细节,也是我们打造高性能前端应用的核心基石~快跟上我,一起探索这场奇妙的页面诞生记吧!🚀

当我们打开 www.baidu.com/ 如何渲染出下面的界面

以 Chrome 浏览器为例

1、浏览器的多进程架构

首先浏览器会分配几个进程,页面的渲染是多个进程之间相互配合的结果

  • 浏览器进程:主要负责界面显示、用户交互,同时提供存储等功能
  • 渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,包含排版引擎 Blink、V8 JS 引擎,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程
  • 网络进程:主要负责页面的网络资源加载
  • GPU 进程:只有一个 GPU 进程,为所有浏览器 Tab 标签页服务。它负责与 GPU 硬件直接通信,执行页面光栅化和合成操作
  • Spare Renderer(Chrome 备用渲染进程): 是一个在后台提前准备好的、空闲的网页渲染进程,目的是提升用户体验和浏览速度而采用的一种优化策略
  • 插件进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离

页面的渲染需要多进程之间相互搭配,重点在于浏览器进程、渲染进程、网络进程、GPU进程之间的配合

2、HTTP 请求过程

浏览器中的 HTTP 请求从发起到结束一共经历了如下八个阶段:

  • 构建请求:GET https://www.baidu.com/ HTTP2

  • 查找缓存:当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载

markdown 复制代码
* 缓存方式:强制缓存/协商缓存 200 304 Cache-Control/Last-Modified/ETag

* 缓存分类:Memory Cache/Disk Cache/Service Worker/Push Cache(HTTP 2)
  • 准备 IP 和端口:请求 DNS 系统返回域名对应的 IP、端口www.baidu.com:170.114.52.116:443
  • 等待 TCP 队列:同一个域名同时最多只能建立 6 个 TCP(http1.1) 连接,其他请求进入排队等待

  • 建立 TCP 连接:三次握手,客户端和服务器总共要发送三个数据包以确认连接的建立

  • 发起 HTTP 请求:HTTP 请求行、请求头、请求体

  • 服务器处理请求/服务器返回请求:返回响应行、响应头、响应体

  • 断开连接:执行"四次挥手"来保证双方都能断开连接

3、从输入 URL 到页面展示

整个过程需要各个进程之间的配合,如下图所示

  • 用户输入:浏览器进程判断输入的关键字是搜索内容,还是请求的 URL,整合成完整的 URL

  • URL 请求过程:浏览器进程会通过 IPC 把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会发起请求,执行步骤二。这里补充几个重要的知识点

    • 响应状态码:301/302 200 304 400 500...

    • 响应类型(Content-Type):服务器返回的响应体数据是什么类

    bash 复制代码
    1.  text/html:服务器返回的数据是 HTML 格式,触发渲染流程
    2.  application/octet-stream:数据是字节流类型的,触发下载流程
    3.  application/json:JSON 字段,前后端常见类型
    4.  text/css|text/javascript |application/javascript:CSS 文件、JS 文件
  • 准备渲染进程:网络进程将响应信息传递给浏览器进程,如果是 text/html,则渲染进程准备接收数据

  • 提交文档:文档可以理解为响应体即返回的 HTML 内容

    • 所谓提交文档,就是指浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程
    • 渲染进程接收到"提交文档"的消息后,会和网络进程建立传输数据的"管道"
  • 渲染阶段:当渲染进程接收到网络进程的响应数据时,便开始页面解析和子资源加载了,边加载边解析

接下来就进入到另外一个部分,渲染进程将网络进程接收到的 HTML + CSS + JS 生成页面,如下图所示

4、页面渲染流程 - 渲染流水线

当网络进程和渲染进程建立管道链接时,渲染进程便可以接收 HTML CSS JS 等数据,接下来渲染进程开始渲染工作。但渲染机制过于复杂,在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段显示成页面,这样的处理流程叫做渲染流水线

按照渲染的时间顺序,流水线可分为如下几个子阶段:

构建 DOM 树、样式计算、布局阶段、分层、绘制、栅格化和合成

4.1 构建 DOM 树

浏览器无法直接理解和使用服务器返回的 HTML,所以需要将 HTML 转换为浏览器能够理解的结构------DOM 树, HTML 经过 HTML 解析器解析,生成树状结构的 DOM

4.2 样式计算

样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成

  1. 把 CSS 转换为浏览器能够理解的结构:styleSheets
  1. 转换样式表中的属性值,使其标准化
  1. 计算出 DOM 树中每个节点的具体样式:继承规则和层叠规则

4.3 布局阶段

现在我们有 DOM 树和 DOM 树中元素的样式,接下来需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局

创建布局树:只包含可见元素的的布局树

  • 渲染进程遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
  • 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容、display:none

布局计算:计算布局树的几何位置

到这里我们完成了渲染流程的前三个阶段:DOM 生成、样式计算和布局,像下图一样

4.4 分层

我们看到的界面都是多个图层相互叠加的结果,类似于 PS 的图层一样

position:fixed、z-indexing 做 z 轴排序都会有新的图层

渲染引擎需要为节点生成专用的图层,并生成一棵对应的图层树(LayerTree),具有几个特点

  • 如果一个节点没有对应的层,那么这个节点就从属于父节点的图层
  • 拥有层叠上下文属性的元素会被提升为单独的一层

4.5 图层绘制

渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示

  • 绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等
  • 而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制
  • 所以在图层绘制阶段,产生的内容就是这些待绘制列表

4.5 栅格化(复杂)

新的名称:合成线程

合成线程 是渲染进程中独立于主线程的一个关键工作线程,它的主要目标是高效地将页面的不同部分合成最终的图像,并发送给GPU进行绘制,从而确保滚动、动画等操作极其流畅

第 1 步:当图层的绘制列表准备好之后,主线程会把图层绘制列表提交给合成线程

两个概念

  • 视口:通常页面很长,用户能看到的部分叫做视口
  • 图块:合成线程会将图层划分为图块,这些图块的大小通常是 256 * 256 px 或者 512 * 512 px

第 2 步:合成线程会为每个图块生成对应的光栅化任务

  • 这个任务包含了"图块的位置、图块所属图层的那部分绘制记录" 的信息
  • 由渲染进程的栅格化线程执行,优先处理视口附近的图块

第 3 步:提交任务到 GPU 进程

  • 栅格化线程将这些光栅化任务以及相关的绘制记录(作为数据源)放入一个队列中
  • 然后,它通过 IPC 将任务队列提交给 GPU 进程。进行 GPU加速

第 4 步:GPU 进程与 GPU 硬件执行光栅化

  • GPU 进程接收任务:GPU 进程收到来自渲染进程(合成线程)的任务队列。
  • 进行光栅化操作

什么是光栅化?

简单说,就是将矢量图形(如绘制记录中的矩形、路径、文字命令)转换为屏幕上的像素(位图)的过程。GPU 进程会驱动 GPU 硬件来执行实际的光栅化工作。它利用 GPU 上强大的、高度并行化的光栅化器来高效完成此任务

  • 结果存储:光栅化后的每个图块存储在 GPU 的显存中

4.6 合成和显示

第 1 步:生成绘制(DrawQuad)指令

  • 当所有(或优先的)图块都光栅化完成后,合成线程会收集每个图块在 GPU 显存中的位置信息(ID)

第 2 步:和浏览器进程通信

  • 浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的合成图像
  • 然后浏览器进程和 GPU 进程通信,发送合成帧

第 3 步:GPU 进程驱动合成

  • GPU 进程接收到合成帧后,会向 GPU 发送最终的绘制命令
  • GPU 根据合成帧的指令,将各个图块纹理(就像一张张图片)绘制到屏幕缓冲区的正确位置上

以上这个过程就是合成 ,它通常通过非常高效的硬件加速操作(如纹理映射)来完成

显示:最终,屏幕的显示控制器从帧缓冲区读取数据,并将像素点亮,用户就看到了最终的页面

结合上图,一个完整的渲染流程大致可总结为如下:

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式
  3. 创建布局树,并计算元素的布局信息
  4. 对布局树进行分层,并生成分层树
  5. 为每个图层生成绘制列表,并将其提交到合成线程
  6. 合成线程将图层分成图块,并在光栅化线程池中配合 GPU 将图块转换成位图
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上

5、拓展内容

5.1 页面的重新渲染(重排、重绘)

来介绍以下前端常说的渲染流水线中两个概念,重排、重绘

1、重排:更新元素的几何位置

例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的

常见的引起重排的方式

  • 页面首次渲染

  • 浏览器窗口大小发生变化

  • 元素的内容发生变化

  • 元素的尺寸或者位置发生变化

  • 元素的字体大小发生变化

  • 添加或者删除可见的DOM元素

2、重绘:更新元素的绘制属性

比如通过 JavaScript 更改某些元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些

常见的引起重绘的方式

  • 改变颜色 (color, background-color)
  • 改变可见性 (visibility)
  • 改变边框样式(如 border-style,但不改变 border-width
  • 文本阴影 (text-shadow)

5.2 性能优化

分为两个阶段:加载阶段和交互阶段、关闭阶段

加载阶段

降低关键资源个数

降低关键资源大小:压缩代码、去除代码注释等

降低关键资源需要多少个 RTT:使用 CDN、缓存

交互阶段

相关推荐
七喜小伙儿1 小时前
第2节:趣谈FreeRTOS--打工人的日常
前端
我叫张小白。1 小时前
Vue3 响应式数据:让数据拥有“生命力“
前端·javascript·vue.js·vue3
laocooon5238578861 小时前
vue3 本文实现了一个Vue3折叠面板组件
开发语言·前端·javascript
IT_陈寒1 小时前
React 18并发渲染实战:5个核心API让你的应用性能飙升50%
前端·人工智能·后端
科普瑞传感仪器2 小时前
从轴孔装配到屏幕贴合:六维力感知的机器人柔性对位应用详解
前端·javascript·数据库·人工智能·机器人·自动化·无人机
n***F8752 小时前
SpringMVC 请求参数接收
前端·javascript·算法
wordbaby2 小时前
搞不懂 px、dpi 和 dp?看这一篇就够了:图解 RN 屏幕适配逻辑
前端
程序员爱钓鱼2 小时前
使用 Node.js 批量导入多语言标签到 Strapi
前端·node.js·trae
鱼樱前端2 小时前
uni-app开发app之前提须知(IOS/安卓)
前端·uni-app