浏览器页面渲染过程

lxf2023-05-10 01:23:31

浏览器的内部结构

如下图所示,8个子系统组合构成了浏览器。

浏览器页面渲染过程

  • 用户界面:包括地址栏、后退/前进按钮、书签目录等
  • 浏览器引擎:查询及操作渲染引擎的接口
  • 渲染引擎:显示请求的内容,比如请求内容为 HTML,则负责解析 HTML 、CSS,并将解析后的结果显示出来
  • 网络子系统:完成网络调用,比如 HTTP 请求,它具有平台无关的接口,可以在不同平台工作
  • Javascript 解释器:解释执行 Javascript 代码
  • XML 解析器:解析 XML
  • 显示后端:绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
  • 数据持久性子系统:属于持久层,浏览器需要在硬盘中保存类似 Cookie 的各种数据

其中页面加载和渲染,离不开浏览器引擎、渲染引擎、网络子系统、JavaScript 解释器

进程与线程

  • 进程是操作系统资源分配的基本单位,进程中包含线程
  • 线程是由进程所管理的,为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型

前端开发平常工作离不开 Chrome 浏览器,现以其为例介绍进程和线程

Chrome 多进程和多线程架构

Chrome 浏览器主要包括4个进程

  • 浏览器进程:处理选项卡之外的内容,用于控制用户可见的 UI 部分(比如地址栏,书签,后退、前进按钮)和用户不可见的隐藏部分(比如网格请求和文件访问),支持多线程

    • UI 线程:绘制浏览器的按钮和输入字段
    • 网络线程:发送请求,接收数据
    • 存储线程:控制对文件的访问
  • GPU 进程:处理图像,3d 绘制,提高性能

  • 渲染进程:每个选项卡都有单独的渲染进程,核心用于渲染页面,支持多线程

    • GUI 渲染线程:渲染浏览器界面
    • JavaScript 引擎线程:解析执行 JavaScript;与 GUI 渲染线程互斥
    • 浏览器定时触发线程:setTimeout、setInterval 相关的线程
    • 浏览器事件触发线程:处理浏览器事件,事件触发后需执行的代码传递给 JavaScript 引擎线程执行
  • 插件进程:管理 Chrome 中安装的插件

浏览器中页面渲染过程

页面导航过程

用户输入 URL,浏览器进程进行请求和准备处理,流程图(包含依赖渲染器进程的过程)如下:

浏览器页面渲染过程

页面渲染过程

获取到资源后,渲染器进程处理选项卡内容的渲染,渲染过程如下图:

浏览器页面渲染过程

渲染过程详解

解析

  • 在解析前,会执行预解析操作,会预先加载 CSS、JS 等文件
  • HTML 解析器解析 HTML,生成 DOM 树
  • CSS 解析器解析 CSS,产生 CSS 规则树
  • 解析 JS 脚本,该过程中需要等待 JS 执行完成才继续解析 HTML

从 HTML 到 DOM

  1. 字节流解码

浏览器通过 HTTP 协议接收到的文档内容是字节数据,通过算法确定字符编码,根据字符编码将字节数据解码成字符数据(即开发编写的代码)

  1. 输入流预处理

将上一步得到的字符数据进行统一格式化,比如将换行符转换成统一格式

  1. 令牌化

将字符数据转化为令牌(Token),不同状态下接收同样的字符数据会产生不同的结果

浏览器页面渲染过程

  1. 构建 DOM 树

浏览器创建解析器的同时,会创建一个 Document 对象

在树构建阶段,Document 作为根节点,不断地被修改和扩充,树构建器接收到某个令牌后,创建该令牌对应的 DOM 元素,并将该元素插入到 DOM 树中

为纠正元素标签嵌套错位,以及处理未关闭的元素标签,树创建器创建的新 DOM 元素还会被插入到一个开放元素栈中

浏览器页面渲染过程

从 CSS 到 CSSOM

渲染引擎解析 CSS 过程与解析 HTML 步骤一致,都会生成树状结构

不同点在于,CSS 在被转换成浏览器能识别的 document.styleSheets 后,还需要操作:

  1. 转换样式表中的属性值,使其标准化:比如颜色值都转换为 rgb 格式,em、rem 转换成 px
  2. 先继承父节点样式,然后进行补充和覆盖

浏览器页面渲染过程

解析 JS

浏览器解析 HTML,当遇到 script 标签时,会立即执行 JS 脚本,停止解析文档,因为 JS 可能改动 DOM 和 CSS

浏览器页面渲染过程

  • 如果遇到的是 script 标签内联代码,解析过程暂停,执行权限给到 Javascript 引擎,执行完再给渲染引擎继续解析

  • 如果遇到的是外链脚本,会等待脚本下载完毕,再继续解析文档,为了减少时间损耗,可以借助 script 标签的2个属性

    • async 属性:立即请求文件,不阻塞渲染引擎,而是文件加载完后阻塞渲染引擎并立即执行文件内容
    • defer 属性:立即请求文件,不阻塞渲染引擎,等解析完 HTML 后再执行文件内容

布局

通过解析之后,浏览器需要进一步渲染页面,就需要进行布局

构建渲染树

DOM 树和 CSSOM 树合并成一棵渲染树

  • 遍历 DOM 树的根节点,在 CSSOM 树上找到每个节点对应的样式
  • 忽略不需要渲染(比如脚本标记、元标记)和不可见的节点(比如设置了 display:none )
  • 添加需要显示的伪类元素到渲染树

计算元素布局

生成渲染树后,需要计算元素的大小及位置,包括字体大小、换行位置等,方便后续绘制过程,获取到每个元素的确切位置和大小

绘制

渲染器线程遍历渲染树,判断元素渲染层级顺序,创建绘制记录

若渲染树发生变化,浏览器触发:

  • 重绘:重画一部分屏幕,元素几何尺寸不变,下面这些操作会导致重绘:

    • 改变 color、background 相关属性
    • 改变 outline 相关属性
    • 改变 border-radius、visibility、box-shadow 等属性
  • 重排:元素几何尺寸改变,重新验证和计算渲染树,成本比重绘高,下面这些操作会导致重排:

    • 浏览器窗口大小发生变化
    • 元素内容、尺寸、位置、字体大小等发生变化
    • 查询某些属性或者调用某些方法
    • 添加或者删除可见的 DOM 元素

为避免降低性能,尽量减少重绘与重排,可通过如下措施

  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作
  • 不要使用 table 布局
  • 不要频繁操作元素样式,对于静态页面,可以修改类名,而不是样式
  • 避免频繁操作 DOM,可通过创建 documentFragment,在其应用 DOM 操作后,再添加到文档
  • 将元素先设置为 display:none,操作结束后再将其显示

光栅化

将计算后的信息(比如文档结构、元素样式、绘制顺序)转换为屏幕上的像素

页面布局变更,触发重排、重绘,则重新进行光栅化,影响性能;为此,浏览器采用合成方法,页面拆分成若干层,分别进行栅格化,再通过合成器线程合成页面,其过程如下:

  1. 主线程创建合成层,确定绘制顺序,将信息传递给合成器线程
  2. 合成器线程栅格化每个图层,将每个图块传递给光栅线程
  3. 光栅线程栅格化每个瓦片,将其存储在 GPU 内存
  4. 合成器线程通过 IPC 提交给浏览器进程,合成器帧传递给 GPU 进程,处理并显示在屏幕上

总结

  1. 浏览器由8个子系统组成
  2. 本文以 Chrome 浏览器为例,介绍其多进程和多线程架构
  3. 掌握浏览器页面渲染过程,有助于前端开发优化页面性能
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!