揭开浏览器渲染的神秘面纱 — 浏览器渲染原理

写在前面

要研究浏览器渲染的原理,我们首先要知道浏览器的主要组成部分浏览器的工作过程

  1. 浏览器的主要组成部分
    • 用户界面:包括标签页、地址栏、导航按钮、收藏夹、书签等,用于用户与浏览器进行交互。
    • JavaScript引擎:JavaScript引擎负责解释和执行网页的 JavaScript 代码。它将 JavaScript 代码转换为计算机能理解的指令,从而实现用户的指定操作以及网页的动态交互。常见的 JavaScript 引擎包括:
      • V8(Google Chrome、Microsoft Edge)
      • SpiderMonkey(Mozilla Firefox)
      • Nitro,也叫做 JavaScriptCore(Apple Safari)
      • Trident(Internet Explorer(但IE浏览器已经无了))
    • 渲染引擎:渲染引擎负责解析和呈现网页内容。它将HTML、CSS和JavaScript等网页元素转换为用户可见的界面。常见的渲染引擎包括:
      • Blink(Google Chrome、Microsoft Edge、Chromium 项目(新版Opera就是基于Chromium 项目的))
      • WebKit(Apple Safari、老版本的Chrome)
      • Gecko(Mozilla Firefox)
      • Trident(Internet Explorer(但IE浏览器已经无了))
    • 布局引擎:布局引擎(属于渲染引擎的一部分)负责处理网页中的排版和布局,将HTML和CSS转换为页面可见的结构,主要的功能包括计算元素的位置、大小、样式等。
    • 网络引擎:网络引擎负责处理网络请求和响应,通过HTTP或HTTPS协议与服务器通信,将网页的内容下载到本地显示。
    • 数据存储:浏览器可以存储一些用户数据,如Cookie、缓存文件、WebStorage、IndexedDB等。这些数据有助于提高网页加载速度和用户体验。
    • 插件和扩展:浏览器允许用户安装插件和扩展,以增加额外的功能和特性。例如,广告拦截器、密码管理器和开发者工具等。
    • 安全组件:浏览器具有安全功能,如阻止恶意网站、标识安全连接和隐私保护等,以确保用户在浏览网页时的安全性和隐私性。
    • 其他辅助功能组件:例如下载管理、历史记录、打印预览等组件
  2. 浏览器的工作过程
    1. 用户输入网址或者搜索内容
    2. 向服务器发起请求
    3. 获取页面资源
    4. 渲染页面
    5. 执行JavaScript
    6. 呈现页面

浏览器渲染过程

  1. 获取HTML资源文件:浏览器通过网络请求获取HTML资源文件
  2. 构建DOM树:浏览器将 HTML 解析成 DOM(文档对象模型) 树,用于表示网页结构和内容。
  3. 构建CSSOM树:浏览器将 CSS 解析成 CSSOM(CSS对象模型)树,用于表示样式规则和层叠关系。
  4. 合并DOM树和CSSOM树,构建渲染树:浏览器将DOM树和CSSOM树合并,生成渲染树(Render Tree),渲染树只包含要显示的元素及其样式(display: none; 的元素不会出现在渲染树中)。
  5. 布局(Layout) :在布局阶段,浏览器会确定每个元素在屏幕上的精确位置和尺寸,考虑到元素的盒模型、浮动、定位等因素。这个阶段也被称为 回流(Reflow)
  6. 绘制(Painting) :在绘制阶段,浏览器会遍历Render树并根据样式信息绘制每个元素的内容,将它们转化为屏幕上的像素。这个阶段也被称为 重绘(Repaint)
  7. 合成与显示:绘制完成后,浏览器会将绘制好的像素传输到图形卡或者操作系统的图形库中,然后在屏幕上显示出来,呈现给用户。

构建DOM树

  1. 词法分析。解析器将HTML或者XML代码分解为一个又一个的标记(tokens),比如开始标签、结束标签、属性、文本节点、注释节点等。
  2. 语法分析。解析器将词法分析得到的标记组合成由一个节点构成的树状结构,整个过程,解析器会根据语言的规则,识别出文档树的结构。

构建CSSOM树

图片来源于blog.csdn.net/cune1359/ar...

浏览器在解析HTML过程中,如果遇到 link 或者 style 就会开始解析 CSS 并构建 CSSOM 树 构建 DOM 树和构建 CSSOM 树是可以并行进行的,两者都发生在浏览器的主渲染进程中。

需要注意的是构建 DOM 树和构建 CSSOM 树是一个比较消耗性能的过程,不同的 CSS 选择器在构建 CSSOM 树时的性能消耗也有不同,CSS 选择器尽量使用 id 选择器和 class 选择器,而且尽量不要过度嵌套。

构建渲染树

当 DOM 树和 CSSOM 树构建完后,就会将二者进行合并,但是合并并不是简单地组合,因为渲染树只包括要显示的元素,因此如果某个元素设置了 display:none 样式,该元素就不会出现在渲染树中。

对比一下 display:nonevisibility: hidden 的区别:

  • display:none
    • 元素不可见,元素会被从文档流中移除(不会渲染)
    • 鼠标悬停和点击无效,伪元素也会被隐藏
    • 可以通过 js 获取该元素
  • visibility: hidden
    • 元素不可见,但是仍然占据着页面布局空间
    • 鼠标悬停和点击无效,伪元素也会被隐藏
    • 可以通过 js 获取该元素

在构建渲染树时,未匹配的 css 规则将会被忽略,优先级被覆盖的规则也会被忽略。

布局

布局阶段,浏览器根据渲染树的节点,计算出 DOM 节点的宽高和位置坐标等信息,生成布局树。布局树中包含伪元素、匿名行盒、匿名块盒等,其生成过程中涉及到 CSS 盒模型、浮动、定位等布局规则。布局过程不是一次性的,它会根据浏览器窗口变化、动态加载等原因重新执行,一般这个过程也称回流。

绘制

在布局完成后,渲染引擎根据渲染树的结构和布局信息,生成绘制指令集(Painting Command)。绘制指令集包含了绘制每个可见元素的具体指令,例如绘制矩形、文本、图像等。

合成并呈现

绘制完成后,浏览器会将绘制好的像素传输到图形卡或者操作系统的图形库中,然后在屏幕上显示出来,呈现给用户。

疑惑解答

浏览器在渲染时遇到JS文件应该怎么处理?

由于 JavaSCript 脚本的执行可能会影响 DOM 结构和内容,为了防止出现意外情况,通常 JavaSCript的加载、解析和执行都会阻塞文档的解析 ,也就是说,当浏览器在构建 DOM 树时,遇到 <script> 标签就会暂停 DOM 树构建(也有特殊情况: async-scriptdefer-script,下面会说明)。

JavaScript 的加载和执行不仅会阻塞 DOM 的构建,也会间接导致 CSSOM 的构建阻塞 DOM 的构建。 因为 JavaScript 不仅可以操作 DOM,也可以修改样式,当 JavaScript 试图读取或者修改样式时,就需要等待 CSSOM 构建完成,这样才能保证 JavaScript 读取到的元素样式是最新。

一般建议将 <script> 标签放到 HTML 元素的后面 </body> 结束标签之前。这样可以确保 JavaScript 不会阻塞 DOM 树的构建并且可以保证 JavaScript 可以顺利操作 DOM,对于用户来说这可以减少白屏的时间,提升用户体验。

为了解决这种阻塞问题,HTML5 引入了 async 和 defer 两个属性来控制脚本的加载和执行。

对比 <script> 标签的 3 种情况的区别:

  1. 同步执行脚本
html 复制代码
<script src="script.js"></script>

在默认情况下,当浏览器遇到这种同步加载的 <script> 标签时,它会阻塞页面的渲染和解析,直到脚本文件被下载、解析和执行完成。这会导致页面渲染的暂停,直到脚本加载和执行完毕。

  1. 异步执行脚本 async。async 属性只对外部脚本生效。
html 复制代码
<script async src="script.js"></script>

使用 async 属性,浏览器会在解析到这个 <script> 标签时,继续解析和渲染页面,而不会等待脚本下载和执行完成。这意味着页面的渲染不会被阻塞,但脚本加载完成后会立即执行,即 异步下载,同步执行 。当 async-script 脚本加载完成,但 HTML 解析还没结束时,那么这时执行的 async-script 就会阻塞页面的渲染。async-script 保证会在页面的 load 事件前执行,但是可能会在 DOMContentLoaded 事件之前或者之后执行(这两个事件接下来会提到)。

需要注意的是,多个异步脚本之间的执行顺序是不确定的,取决于其加载完成的顺序。

  1. 延迟执行脚本 defer。defer 属性只对外部脚本生效。
html 复制代码
<script defer src="script.js"></script>

async-script 脚本一样,defer-script 脚本的加载可以和 HTML 的解析并行,不同的是defer-script 脚本在加载完成后并不会立即执行,而是等待 HTML 解析完成后再执行(浏览器解析到 </html> 结束标签后才执行),即 DOM 树构建完成后就会立即执行。

defer-script 脚本会在 DOMContentLoaded 事件之前执行。多个 defer-script 脚本会按照在文档出现的顺序依次执行。

DOMContentLoaded 和 load

  • DOMContentLoaded 事件会在 DOM 树构建完成后立即执行。这时候所有的 HTML 元素都已经转化为 DOM 了,但是其他的外部资源(样式表、图片等)可能还没加载完成。这个事件标志着这时候可以操作 DOM 了。
js 复制代码
document.addEventListener('DOMContentLoaded', () => {});
  • load 事件会在页面完全加载后触发,这代表了页面所有的外部资源(样式表、图片、脚本等)都已经加载完成了。在此事件中,可以确保页面的所有元素和资源都已准备就绪,可以执行与页面展示和交互有关的操作。
js 复制代码
window.addEventListener('load', () => {});

构建DOM树和构建CSSOM可以并行执行吗?

是的,通常构建 DOM 树和构建 CSSOM 是可以并行执行的。因为 DOM 树和 CSSOM 是两个独立的数据结构,在解析 HTML 的时候,浏览器可以同时下载 CSS 资源并解析构建,前提是没有 JavaScript 阻塞。

为什么 JavaScript 操作 DOM 慢?

因为 JavaScript 在 JS 引擎进程,而 DOM 在渲染进程中,所以 JavaScript 操作 DOM 是一个跨进程的的任务,既然是跨进程通信肯定会存在一定的通信开销的,所以 JavaScript 操作 DOM 会慢。

如何减少首屏渲染的速度?

  • 减少HTTP请求次数:合并或减少资源文件的数量,使用CSS雪碧图合并小图标。
  • 合理使用浏览器缓存:使用浏览器缓存来存储资源,使得用户首次访问后,再次访问时可以直接从缓存加载资源,减少网络请求。
  • 优化图片:合理使用图片格式或者通过压缩图片,减少图片的大小。
  • 按需加载资源:对于首屏用不到的资源就尽量不加载,对于图片资源可以利用 data-src 代替 src 实现图片懒加载。
  • 压缩资源文件:对CSS和JS等资源文件进行压缩,去除代码中的注释、多余的空格、不必要的换行等。
  • 使用内容分发网络(CDN)技术: 使用内容分发网络(CDN)来分发网页资源,以确保资源能够从离用户更近的服务器加载,从而提高加载速度。
  • 异步加载脚本: 将不影响页面初始化的 JavaScript 代码推迟到页面加载后再执行,以确保首屏渲染不受阻碍。

什么是回流(重排)?

回流(Reflow ),又称重排。当浏览器重新计算网页上某些元素的位置、宽高、布局时就会发生重排。

触发回流的情况

  • 页面首次渲染。
  • 浏览器窗口大小发生改变时(resize 事件发生时)。
  • 增减、移除、显示、隐藏页面元素
  • 页面元素的位置发生改变
  • 页面元素的尺寸发生改变(边距、填充、边框、宽度和高度)
  • 页面元素的内容发生改变(包括输入框内容发生改变)
  • 读取元素的 offsetTopoffsetLeftoffsetWidthoffsetHeightclientTopclientLeftclientWidthclientHeightscrollTopscrollLeftscrollWidthscrollHeight
  • 调用 window.getComputedStyle() 方法

怎么减少回流(重排)?

  • 尽量避免多次单独修改DOM元素。可以通过创建文档碎片来代替多次增加DOM节点的操作;当一定要多次修改DOM时,可以先将DOM隐藏后再操作,操作完成后再恢复。
  • 使用 CSS 动画。CSS 动画(例如 transform 和 opacity)不会触发回流,因为它们只涉及到图层的重绘,而不是布局的改变。transform 会调用GPU硬件加速。
  • 避免使用table元素和table布局 。因为 table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算。
  • 使用文档碎片。在 DOM 中预先创建一个文档碎片,将多个操作都在文档碎片中完成,然后再一次性添加到文档中,减少实际的 DOM 修改次数。
  • 批量修改样式。避免频繁单个修改 DOM 元素的样式,可以将多个样式修改合并成一个操作,减少回流次数。(例如:直接修改元素的 class 等)
  • 使用绝对定位或固定定位。这可以将元素脱离文档流,减少对其他元素布局的影响。
  • 避免频繁的布局查询。 尽量避免使用会触发布局查询的属性,比如 offsetTop、offsetLeft、clientWidth 等

什么是重绘?

重绘(Repaint)是指在页面上某些元素的可见部分发生变化时,浏览器需要重新绘制这些元素的过程。回流一定会发生重绘,发生重绘不一定会发生回流。

常见引起重绘属性和方法(但不引起重排的):

color border-style visibility text-decoration
background background-image background-position background-repeat
background-size outline-color outline outline-style
outline-radius outline-width box-shadow
相关推荐
雯0609~3 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ6 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z12 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜36 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40436 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish37 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five38 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序38 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54139 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
前端每日三省40 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript