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

写在前面

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

  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
相关推荐
SunTecTec33 分钟前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
拉不动的猪2 小时前
前端常见数组分析
前端·javascript·面试
小吕学编程2 小时前
ES练习册
java·前端·elasticsearch
Asthenia04122 小时前
Netty编解码器详解与实战
前端
袁煦丞2 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛3 小时前
vue组件间通信
前端·javascript·vue.js
一笑code3 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员4 小时前
layui时间范围
前端·javascript·layui
NoneCoder4 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19704 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端