浏览器渲染页面的原理及过程

本文详细介绍了浏览器如何将HTML文件转化为可视化的网页,包括构建DOM树、CSSOM树、渲染树的过程,以及JavaScript对渲染的影响,解释了重绘与重排的概念,并探讨了常见的渲染问题。

浏览器将域名通过网络通信从服务器拿到html文件后,如何渲染页面呢?

1.根据html文件构建DOM树和CSSOM树。构建DOM树期间,如果遇到JS,阻塞DOM树及CSSOM树的构建,优先加载JS文件,加载完毕,再继续构建DOM树及CSSOM树。

2.构建渲染树(Render Tree)。

3.页面的重绘(repaint)与重排(reflow,也有称回流)。页面渲染完成后,若JS操作了DOM节点,根据JS对DOM操作动作的大小,浏览器对页面进行重绘或是重排。

一、构建DOM树及CSSOM树

  1. 构建DOM树

HTML 文档中的所有内容皆是节点,各节点之间拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成DOM树。最常见的几种节点有:文档节点、元素节点、文本节点、属性节点、注释节点。

DOM节点树中节点与HTML文档中内容一一对应,DOM树构建过程:读取html文档,将字节转换成字符,确定tokens(标签),再将tokens转换成节点,以节点构建 DOM 树。如下图所示:
在这里插入图片描述

  1. 构建CSSOM树

CSS文档中,所有元素皆是节点,与HTML文件中的标签节点一一对应。CSS中各节点之间同样拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成CSSOM树。

在构建DOM树的过程中,在 HTML 文档的 head 标签中遇到 link 标签,该标签引用了一个外部CSS样式表。由于预见到需要利用该CSS资源来渲染页面,浏览器会立即发出对该CSS资源的请求,并进行CSSDOM树的构建。

CSSOM树构建过程与DOM树构建流程一致:读取CSS文档,将字节转换成字符,确定tokens(标签),再将tokens转换成节点,以节点构建 CSSOM 树。如下图所示:
在这里插入图片描述

  1. 加载JS

若在构建DOM树的过程中,当 HTML 解析器遇到一个 script 标记时,即遇到了js,将立即阻塞DOM树的构建,将控制权移交给 JavaScript 引擎,等到 JavaScript 引擎运行完毕,浏览器才会从中断的地方恢复DOM树的构建。
其根本原因在于,JS会对DOM节点进行操作,浏览器无法预测未来的DOM节点的具体内容,为了防止无效操作,节省资源,只能阻塞DOM树的构建。譬如,若不阻塞DOM树的构建,若JS删除了某个DOM节点A,那么浏览器为构建此节点A花费的资源就是无效的。

若在HTML头部加载JS文件,由于JS阻塞,会推迟页面的首绘。为了加快页面渲染,一般将JS文件放到HTML底部进行加载,或是对JS文件执行async或defer加载。
在这里插入图片描述

二.构建渲染树

渲染树(Render Tree)由DOM树、CSSOM树合并而成,但并不是必须等DOM树及CSSOM树加载完成后才开始合并构建渲染树。三者的构建并无先后条件,亦非完全独立,而是会有交叉,并行构建。因此会形成一边加载,一边解析,一边渲染的工作现象。

构建渲染树,根据渲染树计算每个可见元素的布局,并输出到绘制流程,将像素渲染到屏幕上。

问题一:渲染过程中遇到JS文件怎么处理?

JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。

也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性(下文会介绍这两者的区别)。

JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。

原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。

这是什么情况?

这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。前面我们介绍,不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。

问题二:为什么操作 DOM 慢

因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

问题三:渲染页面时的常见不良现象(FOUC、白屏)

FOUC : 主要指的是样式闪烁的问题。即CSS加载之前,先加载了HTML,则出现了“先展示无样式内容,然后再突然呈现样式”的现象。原因是:CSS加载时间过长、CSS放在文档底部

FOUC (Flash of Unstyled Content) 无样式内容闪烁:
如果把样式放在底部,对于IE浏览器,在某些场景下(点击链接,输入URL,使用书签进入等),会出现 FOUC 现象(逐步加载无样式的内容,等CSS加载后页面才突然展现出样式)。对于 Firefox 会一直表现出 FOUC 。
由于:
脚本会阻塞后面内容的呈现
脚本会阻塞其后组件的下载

对于图片和CSS, 在加载时会并发加载(如一个域名下同时加载两个文件)。但在加载 JavaScript 时,会禁用并发,并且阻止其他内容的下载

所以尽量把 JavaScript 放入页面body底部。

白屏:浏览器迟迟未渲染页面。有的浏览器需要先构建DOM树和CSSOM树,构建完成再渲染。有可能因为CSS放在HTML尾部,CSS未加载完成,则就不能构建完成,从而不能渲染而白屏;也有可能是JS放在了头部,阻塞了DOM的解析

  1. 白屏的根本原因是浏览器在渲染的时候没有请求到或请求时间过长造成的。
  2. 浏览器对于图片和CSS,在加载时会并发加载(比如一个域名下同时加载多个文件),浏览器对于JavaScript,在加载时会禁用并发,并且阻止其后的文件及组件的下载。所以将js放在页面的顶部也可能会导致白屏。
  3. 不同浏览器的处理CSS和HTML的方式是不同的:
    比如,IE、chrome浏览器的渲染机制,采用的是等CSS全部加载解析完后再渲染展示页面。
    Firefox则是在CSS未加载前先展示html的内容,等CSS加载后重新对样式进行修改。

所以:白屏的出现情况往往因为CSS样式被置于底部(最后加载),当新窗口打开,刷新等的时候,页面会出现白屏。 如果使用
@import标签,它引用的文件则会等页面全部下载完毕再被加载,也可能出现白屏。

因此, css使用 link 标签将样式表放在顶部,防止白屏问题出现。 JS 的放置位置一般是在body的闭合标签之前。

### 浏览器渲染原理过程详解 #### 渲染引擎与JavaScript引擎的作用 浏览器内核主要包括两个核心组件:**渲染引擎**和**JavaScript引擎**。以Chrome浏览器为例,其渲染引擎为 **Blink**(前身是 WebKit),负责解析 HTML 和 CSS,并将内容绘制到屏幕上;而 JavaScript 引擎则是 **V8**,用于执行页面中的脚本逻辑 [^2]。 渲染引擎的主要任务包括: - 解析 HTML 文档生成 DOM 树。 - 解析 CSS 生成 CSSOM(CSS Object Model)树。 - 将 DOM 和 CSSOM 合并生成渲染树(Render Tree)[^4]。 - 根据渲染树进行布局计算(Layout/Reflow),确定每个元素的几何位置。 - 绘制像素到屏幕上(Painting)。 - 最后进行合成(Composite),将多个图层组合成最终画面。 #### 构建DOM树与CSSOM树 当浏览器接收到 HTML 响应后,会从上至下逐行解析 HTML 内容,构建出一棵 DOM 树。这棵树反映了文档中所有节点的层级关系。与此同时,如果遇到 `<style>` 或外部链接如 `<link rel="stylesheet">`,则会开始解析 CSS 并生成 CSSOM 树。CSSOM 不仅包含样式规则,还体现了选择器之间的嵌套结构和优先级关系 [^4]。 值得注意的是,在解析过程中,若主线程遇到 `<link>` 标签引入的外部 CSS 文件,它不会等待该文件下载完成,而是继续解析后续 HTML。这是由于 CSS 的下载和解析是由预加载扫描器(Preloader Scanner)单独处理的线程所负责,从而避免阻塞主 HTML 的解析流程 [^5]。 #### 渲染树的构建 在 DOM 和 CSSOM 都准备好之后,两者会被合并形成渲染树。渲染树只包含需要显示的节点及其对应的样式信息。例如,`<head>` 中的内容或设置了 `display: none` 的元素不会出现在渲染树中。这一阶段完成后,浏览器就能知道哪些内容需要被绘制以及如何绘制。 #### 回流(Reflow)与重绘(Repaint) 一旦渲染树建立完毕,浏览器便进入布局阶段。此阶段会对渲染树上的每一个可见元素进行几何尺寸和位置的计算,这个过程被称为 **回流(Reflow)**。任何影响页面布局的操作都会触发回流,比如修改宽高、调整边距、改变字体大小等。此外,某些用户交互行为如鼠标悬停、点击事件也可能导致回流的发生。回流几乎不可避免,且通常会影响到整个文档树的一部分甚至全部 [^3]。 随后,浏览器进入 **重绘(Repaint)** 阶段,即将计算好的几何属性转换为屏幕上的实际像素值。虽然重绘比回流耗时较短,但如果频繁发生仍然会导致性能问题。为了保证良好的用户体验,现代浏览器对每一帧的渲染工作都有严格的时间限制——大约每秒60帧,即每次渲染不得超过约16毫秒。超过这个时间就会出现卡顿现象,直接影响用户的视觉感受 [^1]。 #### 主线程的工作机制 整个渲染流程主要由浏览器的主线程来驱动。该线程不仅负责 HTML/CSS 的解析,还要处理 JavaScript 执行、布局计算、绘制操作以及其他异步任务。因此,过多复杂的计算或者长时间运行的脚本可能会占用主线程资源,进而延迟其他关键任务的执行,造成页面响应迟缓甚至冻结。 #### 示例代码:模拟简单渲染流程 以下是一个简化的示例,演示了如何使用 JavaScript 来观察页面渲染的基本步骤: ```javascript // 创建一个简单的 div 元素并添加到 body const newDiv = document.createElement('div'); newDiv.textContent = 'Hello, rendering process!'; document.body.appendChild(newDiv); // 修改样式以触发 reflow 和 repaint newDiv.style.color = 'red'; newDiv.style.fontSize = '20px'; // 强制浏览器立即执行 layout 计算 console.log(newDiv.offsetHeight); // 这里会强制同步 layout ``` 上述代码创建了一个新的 `<div>` 元素,并通过修改其样式属性来触发回流与重绘。最后通过访问 `offsetHeight` 属性迫使浏览器立刻完成一次布局计算。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值