一、前言
前端开发中,我们经常会听到这些词:加载(Loading) 、解析(Parsing) 、渲染(Rendering) ,以及两个重要的事件:DOMContentLoaded 和 onload 。
它们看似接近,却分别属于浏览器渲染流程中的不同阶段。
如果不能真正理解这几个概念,就很容易在性能优化、事件绑定、懒加载等场景中产生误区。
本文将带你从浏览器渲染原理出发,彻底理清这些概念的本质与顺序。
二、HTML 从加载到渲染的整体流程
当浏览器开始加载一个网页时,大致会经历以下几个阶段:
-
加载资源(Loading)
浏览器根据 HTML 中的内容,请求并下载 HTML 文件本身,以及其中引用的 CSS、JS、图片、字体等资源。
-
解析结构(Parsing)
浏览器开始自上而下地解析 HTML 文本,构建 DOM 树(Document Object Model) 。
同时,遇到 CSS 文件则下载并解析,生成 CSSOM 树(CSS Object Model) 。
DOM 与 CSSOM 合并后,形成用于渲染的 Render Tree(渲染树)。
-
布局与绘制(Layout & Paint)
渲染树确定每个元素在页面中的几何位置与大小(布局),再将其绘制到屏幕上(绘制)。
-
复合与渲染(Composite & Render)
浏览器将各图层进行合成、优化,并最终显示给用户。
整个过程并不是线性的"等待全部加载完再渲染",而是边解析边渲染 的流式执行。
这意味着:浏览器一旦解析到可绘制的内容(如文字或结构),就会立即开始渲染首屏。
三、加载(Loading)
"加载"指的是浏览器从网络请求并获取资源的过程 。
包括:
-
主 HTML 文档的下载;
-
HTML 中引用的 CSS、JS、图片、字体等静态资源的下载。
加载并不代表内容被展示,也不代表资源已经生效。
例如,一个 JS 文件可能早已下载完毕,但由于带有 defer 或 async,尚未执行。
一个 CSS 文件可能下载完成,但还未解析成 CSSOM,也就还不能影响布局。
四、解析(Parsing)
"解析"是浏览器将文本内容转换为结构化数据的过程。
这一步可以分为两部分:
-
解析 HTML → 构建 DOM 树
浏览器读取 HTML 文本,从
<html>开始逐行扫描并生成节点。但如果遇到
<script>标签(尤其是没有async或defer的情况),会暂停 DOM 解析,先执行脚本,再继续。 -
解析 CSS → 构建 CSSOM 树
浏览器下载并解析所有外部样式与内联样式,构建出样式规则树。
注意:CSSOM 构建完成之前,页面无法完成渲染,因为样式信息尚不完整。
解析阶段是"结构理解"的过程。
只有当 DOM 和 CSSOM 都构建完毕,浏览器才具备完整的页面信息。
五、渲染(Rendering)
"渲染"是浏览器根据 DOM 和 CSSOM 生成视觉结果的过程。
主要包含三步:
-
构建渲染树(Render Tree)
将可见元素(例如非
display:none的节点)组合起来。 -
布局(Layout)
确定每个元素的大小与位置。
-
绘制(Paint)
将像素渲染到屏幕上。
这就是用户真正"看到页面"的时刻。
从 首次绘制(First Paint, FP) 到 最大内容绘制(LCP),都是衡量渲染阶段性能的关键指标。
六、DOMContentLoaded 与 onload 的区别
理解这两个事件,就要看浏览器在哪个阶段触发它们。
| 事件 | 触发时机 | 是否等待样式 | 是否等待图片等资源 | 是否等待 defer 脚本 |
|---|---|---|---|---|
| DOMContentLoaded | DOM 树构建完成后 | ✅ 需要等待 CSS 解析 | ❌ 不等待图片、iframe 等 | ✅ 等待所有 defer 脚本执行 |
| load(onload) | 页面及其所有资源加载完成后 | ✅ | ✅ 包括图片、CSS、字体、iframe 等 | ✅ |
总结一句话:
-
DOMContentLoaded表示"文档结构准备就绪"; -
onload表示"页面完全加载完成"。
所以如果你想在DOM 可用但资源未加载完成时 执行初始化逻辑(如挂载事件),用 DOMContentLoaded 更合适。
如果你需要依赖图片、CSS 等资源(如轮播图尺寸计算),则用 window.onload。
移除全局 Loading 通常放在 window.onload 事件里,确保:
-
HTML 文档已经解析完毕;
-
所有 CSS、图片、字体、iframe 等资源已经加载完成;
-
页面完整可见,用户体验最佳。
ocument.addEventListener('click', handler) 通常用于全局点击事件委托,比如:
-
阻止冒泡或处理模态框关闭;
-
页面统计点击事件(埋点);
-
菜单、下拉框等交互。
为什么可以放在 DOMContentLoaded:
-
DOMContentLoaded触发时,DOM 树已经构建完毕,也就是说页面上的大部分元素都存在于 DOM 中; -
绑定全局事件时,不需要等待图片或其他资源加载完成;
-
可以保证交互逻辑在用户可以操作页面时尽早生效。
七、顺序与事件时间线
以一个典型页面为例,时间线大致如下:
HTML 加载开始
↓
HTML 解析 → 构建 DOM
↓(遇到外部样式)下载并解析 CSS
↓(遇到 <script>)执行 JS(可能阻塞)
↓
DOM 完成 → 触发 DOMContentLoaded
↓
等待所有资源加载完毕(图片、iframe、字体等)
↓
触发 window.onload
↓
后续用户交互与渲染更新(Reflow / Repaint)
八、总结
| 概念 | 作用 | 特点 |
|---|---|---|
| 加载(Loading) | 下载 HTML 与资源 | 仅表示"已获取",未必生效 |
| 解析(Parsing) | 构建 DOM 与 CSSOM | 结构化阶段,JS 可能阻塞 |
| 渲染(Rendering) | 将内容绘制到屏幕 | 依赖 DOM + CSSOM |
| DOMContentLoaded | DOM 树构建完毕触发 | 不等图片,但等 defer 脚本 |
| onload | 所有资源加载完毕触发 | 页面完全就绪 |
掌握这些阶段的区别,是理解浏览器工作机制的基础。
无论是优化首屏渲染、监控性能指标,还是正确绑定初始化逻辑,理解"加载---解析---渲染"的边界,都是前端进阶的必经之路。