外观
浏览器渲染原理深度探究
本文转载自 解构现代网络:深度探究浏览器的渲染原理,作者不详。
第 1 节:关键渲染路径:⼀个⾼层框架
本节旨在为整个渲染过程建⽴⼀个概念模型。它引⼊了关键渲染路径(Critical Rendering Path, CRP)作为浏览器将 HTML、CSS 和 JavaScript 代码转换为屏幕上像素所必须执⾏的⼀系列步骤。优化此路径对于实现快速的初始⻚⾯加载⾄关重要。
1.1 定义关键渲染路径 (CRP)
关键渲染路径(CRP)是浏览器为了渲染⻚⾯的初始视图⽽必须完成的⼀系列事件 1。它涉及处理 HTML、CSS 和 JavaScript,以构建渲染所需的必要数据结构 3。CRP 优化的核⼼⽬标是最⼤限度地减少⽤⼾凝视空⽩屏幕的时间,从⽽提升感知性能、⽤⼾参与度和转化率 5。
这个过程本质上是顺序性的,并由依赖关系驱动:HTML 的解析会触发对资源的发现(如 CSS 和 JavaScript),⽽这些资源⼜是后续渲染阶段所必需的 3。这种内在的依赖关系揭⽰了浏览器设计中的⼀个根本性权衡:对速度的追求与对正确性的要求之间的平衡。浏览器对 HTML 的增量解析 3 是⼀个为速度⽽⽣的优化,使其能够⽴即开始⼯作。然⽽,CSS 和 JavaScript 的渲染阻塞特性 3 则是为了保证正确性。浏览器不能在样式未知的情况下渲染内容,否则会导致“⽆样式内容闪烁”(Flash of Unstyled Content, FOUC),也不能在依赖于特定样式或 DOM 结构脚本执⾏前渲染⻚⾯。
为了保证渲染结果的正确性并避免⽆效的计算⼯作,浏览器必须在 CSSOM(CSS 对象模型)构建完成之前阻塞渲染。例如,如果浏览器在获取并解析完所有 CSS 之前就渲染了初始的 HTML,那么⼀个位于外部 CSS ⽂件末尾的规则,如 body { display: none; },将使得之前所有的渲染⼯作都变得毫⽆意义。浏览器需要丢弃已渲染的像素,并重新进⾏整个渲染流程,这不仅效率低下,也带来了极差的⽤⼾体验。因此,这个由 HTML 指向 CSS,再由 CSS 指向最终渲染的依赖链,正是关键渲染路径的核⼼,也是性能优化的关键所在。理解这⼀点,就将 CRP 优化从简单的“让⼀切变快”提升到了“智能地管理依赖图”的层⾯。
1.2 CRP 的核⼼阶段
关键渲染路径可以概括为以下五个核⼼阶段:
- 解析 HTML (Parsing HTML):处理 HTML 标记,构建 DOM 树(Document Object Model Tree)。
- 解析 CSS (Parsing CSS):处理 CSS 标记,构建 CSSOM 树(CSS Object Model Tree)。
- 合并 (Combining):将 DOM 和 CSSOM 合并成渲染树(Render Tree)。
- 布局 (Layout):基于渲染树,计算每个节点的⼏何信息(⼤⼩和位置)。
- 绘制 (Paint):将各个节点绘制到屏幕上,转换为实际的像素 4。
必须强调,这是⼀个渐进式的过程。为了提供更佳的⽤⼾体验,渲染引擎会⼒求尽快将内容显⽰在屏幕上。它不必等到整个 HTML ⽂档解析完毕之后,才开始构建渲染树和进⾏布局 4。⼀旦浏览器接收到最初的数据块,它就会开始解析并尝试渲染其所拥有的内容。
1.3 分析 CRP 的关键指标
为了量化和优化关键渲染路径,我们需要理解以下⼏个关键指标:
- 关键资源 (Critical Resources):任何可能阻塞⻚⾯⾸次渲染的资源。这包括 HTML ⽂档本⾝、阻塞渲染的 CSS ⽂件以及同步执⾏的 JavaScript 2。
- 关键路径⻓度 (Critical Path Length):获取所有关键资源所需的⽹络往返次数(Round-Trip Time, RTT)或总时间。例如,⼀个包含外部 CSS ⽂件的简单⻚⾯,其关键路径⻓度⾄少为两次⽹络往返:⼀次⽤于获取 HTML,⼀次⽤于获取 CSS 5。
- 关键字节数 (Critical Bytes):完成⻚⾯⾸次渲染所需的总字节数,即所有关键资源传输⽂件⼤⼩的总和。减少关键字节数是优化下载时间的核⼼策略之⼀ 4。
对这些指标的分析,使得优化策略变得具体化。例如,通过内联关键 CSS、使⽤媒体查询延迟⾮关键 CSS 的加载 8,以及为 JavaScript 使⽤ async 或 defer 属性 5,都是旨在缩短关键路径⻓度、减少关键资源数量和关键字节数的有效⼿段,其本质都是在重构或打破原有的依赖关系,以解除对初始渲染的阻塞。
第 2 节:解析与对象模型的构建
本节将深⼊剖析关键渲染路径的前两个步骤:将基于⽂本的原始 HTML 和 CSS ⽂件,转换为浏览器引擎能够理解和操作的、结构化的内存中表⽰形式——即对象模型。
2.1 ⽂档对象模型 (DOM)
2.1.1 解析过程与增量构建
当浏览器从⽹络接收到 HTML 数据后,其主线程会⽴即开始解析⼯作。这个过程将原始的字节流转换为结构化的 DOM 树,⼤致经历以下步骤:字节 (Bytes) → 字符 (Characters) → 标记 (Tokens) → 节点 (Nodes) → DOM 树 3。DOM 是浏览器对⻚⾯的内部表⽰,也是 Web 开发者通过 JavaScript 与之交互的数据结构和 API 9。
DOM 的构建是增量式的。浏览器⽆需等待整个 HTML ⽂档下载完成,只要接收到第⼀个数据块,就可以开始解析并构建 DOM 树 3。这种流式处理的⽅式,使得⽤⼾能够更快地看到⻚⾯的部分内容。
2.1.2 容错机制
HTML 解析器具有极强的容错性。与严格的编程语⾔不同,浏览器在处理不规范的 HTML 标记时,⼏乎从不抛出错误。例如,⼀个缺少闭合标签的 <p> 元素,或者⼀个错误的嵌套 <b><i>text</b></i>,都会被浏览器根据 HTML 规范中定义的错误处理算法进⾏“修复”,最终⽣成⼀个有效的、可预测的 DOM 树,如将后者修正为 <b><i>text</i></b> 9。这种设计哲学确保了绝⼤多数⽹⻚,⽆论其代码质量如何,都能在浏览器中正常显⽰。
2.1.3 预加载扫描器
为了进⼀步加速资源的获取,现代浏览器在主 HTML 解析器⼯作的同时,还会并发运⾏⼀个“预加载扫描器”(Preload Scanner)。这个扫描器会提前扫描 HTML ⽂档中的内容,查找像 <link>、<script>、<img> 这样的资源引⽤标签。⼀旦发现,它会⽴即向浏览器进程中的⽹络线程分派请求,⽽⽆需等待主解析器正式处理到这些标签。这项关键优化实现了资源获取与 HTML 解析的并⾏化,极⼤地缩短了关键资源的加载延迟 9。
2.2 CSS 对象模型 (CSSOM)
2.2.1 构建过程与渲染阻塞
CSSOM 是浏览器将 CSS 规则解析后⽣成的、⽤于表⽰⻚⾯所有样式的对象模型。与 DOM 类似,它也是⼀个树状结构,包含了如何展⽰ DOM 元素的信息 3。
然⽽,与 DOM 的增量构建不同,CSSOM 的构建过程具有根本性的渲染阻塞特性。浏览器必须等待所有(⾮延迟加载的)CSS ⽂件被下载、解析并构建成完整的 CSSOM 树之后,才能进⼊后续的渲染环节 3。其根本原因在于 CSS 的“层叠”(Cascading)特性:后⾯的规则可以覆盖前⾯的规则。在所有 CSS 规则都明确之前,任何元素的最终样式都是不确定的。因此,浏览器⽆法使⽤⼀个不完整的 CSSOM 来进⾏渲染,否则可能导致样式错误。所以,CSSOM 的构建在逻辑上是“⾮增量”的,必须整体完成后才能使⽤ 3。
2.3 层叠、优先级与继承:样式解析的逻辑
浏览器在为⼀个元素计算最终样式时,遵循⼀套严谨⽽复杂的规则体系,以解决来⾃不同源的样式声明之间的冲突。
2.3.1 层叠顺序(来源与重要性)
样式冲突解决的⾸要依据是其来源(Origin)和重要性(Importance)。浏览器将所有声明分为多个优先级组。⼀个简化的优先级顺序如下:
- 正在过渡的属性 (Transitioning properties)
- !important 标记的⽤⼾代理样式 (浏览器默认样式)
- !important 标记的⽤⼾样式 (⽤⼾⾃定义样式)
- !important 标记的作者样式 (开发者定义的样式)
- 正在动画的属性 (Animating properties)
- 常规的作者样式
- 常规的⽤⼾样式
- 常规的⽤⼾代理样式
这个严格的层级确保了例如⽤⼾为了可访问性⽽设置的 !important 样式能够覆盖开发者设计的⻚⾯样式 10。
2.3.2 层叠层 (@layer)
@layer 是⼀个现代 CSS 特性,它允许开发者在“作者样式”这⼀来源内部,显式地定义多个⼦层级。这为管理来⾃不同库、框架或组件的 CSS 提供了前所未有的控制⼒。层的声明顺序决定了其优先级:对于常规样式,后声明的层会覆盖先声明的层中的样式,⽆论其内部选择器的优先级如何。这使得开发者可以更宏观地组织和控制样式表的结构,⽽不必陷⼊复杂的优先级战争 10。
2.3.3 优先级 (Specificity)
在同⼀个来源和层级内部,选择器的优先级是下⼀个裁决标准。它是⼀个衡量选择器精确程度的加权系统,通常可以看作⼀个三元组 (A, B, C) 11:
- A (ID选择器): 选择器中每出现⼀个 ID 选择器(如 #header),A 值加 1。
- B (类、属性、伪类选择器): 每出现⼀个类选择器(如 .nav-item)、属性选择器(如[type="button"])或伪类(如 :hover),B 值加 1。
- C (元素、伪元素选择器): 每出现⼀个元素选择器(如 div)或伪元素(如 ::before),C 值加 1。
⽐较优先级时,从 A 开始逐位⽐较,值⼤者胜出。只有在上⼀位完全相等时,才⽐较下⼀位。
| 选择器(Selector) | ID(A) | 类/属性/伪类(B) | 元素/伪元素(C) | 计算出的优先级 |
|---|---|---|---|---|
| h1 | 0 | 0 | 1 | 0-0-1 |
| h1 + p::first-letter | 0 | 0 | 3 | 0-0-3 |
| li > a >.inline-warning | 0 | 2 | 2 | 0-2-2 |
| #main-nav | 1 | 0 | 0 | 1-0-0 |
| button:not(#mainBtn,.cta) | 1 | 1 | 1 | 1-1-1 |
2.3.4 源码顺序 (Source Order)
当两个规则的来源、层级和优先级完全相同时,最后的裁决标准是它们在样式表中出现的顺序。后出现的规则将覆盖先出现的规则 10。
2.4 JavaScript 的中断与交互
JavaScript 的引⼊为渲染过程增加了更多的复杂性和依赖关系,形成了⼀个性能瓶颈的“依赖三⻆”。
2.4.1 解析器阻塞
当 HTML 解析器在⽂档中遇到⼀个常规的 <script> 标签(即没有 async 或 defer 属性)时,它必须暂停 DOM 的构建。如果脚本是外部的,浏览器会先下载它,然后执⾏脚本,最后才能恢复 HTML的解析 7。这种阻塞⾏为是必要的,因为脚本可能会通过
document.write() 等⽅法直接修改 DOM 结构,解析器必须在继续⼯作前获知这些修改。
2.4.2 CSSOM 阻塞 JavaScript
JavaScript 常常需要查询元素的样式信息,例如通过 element.getComputedStyle()。为了返回⼀个准确⽆误的值,浏览器必须确保所有相关的 CSS 规则都已经被解析和应⽤。因此,如果⼀个脚本在CSSOM 仍在构建时尝试执⾏,浏览器会暂停该脚本的执⾏,直到 CSSOM 构建完成。这意味着,CSSOM 不仅阻塞渲染,同时也阻塞了 JavaScript 的执⾏ 4。
这种连锁反应是关键渲染路径中⼀个典型的性能陷阱。⼀个加载缓慢的外部 CSS ⽂件,会阻塞CSSOM 的构建;不完整的 CSSOM 会阻塞后续 JavaScript 的执⾏;⽽这个被阻塞的 JavaScript(如果是同步脚本)⼜会阻塞 HTML 解析器的后续⼯作。整个⻚⾯的构建过程因此陷⼊停滞。这充分说明了为什么“优化 <head> 元素”对于⾸屏性能⾄关重要 2。
2.4.3 使⽤ async 和 defer 缓解阻塞
为了打破这种阻塞,开发者可以为 <script> 标签添加 async 或 defer 属性:
async: 脚本的下载会异步进⾏,不会阻塞 HTML 解析。下载完成后,它会⽴即执⾏。这种⽅式的缺点是脚本的执⾏时机不确定,可能会在 DOM 未完全构建时执⾏,且多个 async 脚本之间的执⾏顺序也⽆法保证 5。
defer: 脚本的下载同样是异步的,但其执⾏会被推迟到整个 HTML ⽂档解析完成之后、DOMContentLoaded 事件触发之前。多个 defer 脚本会按照它们在⽂档中出现的顺序依次执⾏。这使得 defer 成为⼤多数⾮关键脚本的⾸选⽅案,尤其是那些需要操作完整 DOM 的脚本 4。
第 3 节:渲染流⽔线:从抽象树到屏幕像素
本节将详细阐述渲染过程的后半部分,即浏览器如何将抽象的 DOM 和 CSSOM 数据结构,通过⼀系列操作,最终转换为⽤⼾屏幕上可⻅的像素。
3.1 渲染树的构建
渲染流程的第⼀步是合成。浏览器将 DOM 树和 CSSOM 树结合起来,创建⼀个新的树结构,称为“渲染树”(Render Tree),在⼀些现代浏览器(如 Chromium)的语境下,也常被称为“布局树”(Layout Tree) 3。
3.1.1 可⻅性是关键
渲染树的构建原则是“所⻅即所得”。它只包含那些最终需要被渲染到屏幕上的对象。浏览器会从 DOM 树的根节点开始遍历,并为每个可⻅节点构建⼀个对应的渲染树节点 6。
3.1.2 被忽略的元素
在构建过程中,以下⼏类元素会被忽略,不会出现在渲染树中:
- ⾮视觉元素: 那些本⾝不产⽣视觉输出的元素,如
<head>、<script>、<meta>、<link>等,会被完全跳过 3。 - display: none 的元素: 任何通过 CSS 设置了 display: none; 的节点,以及它的所有后代节点,都会从渲染树中移除。这些元素被认为在布局上不占据任何空间 3。
3.1.3 包含但不可⻅的元素
⼀个重要的区别是,使⽤ visibility: hidden; 隐藏的元素会被包含在渲染树中。尽管它们在视觉上不可⻅,但它们依然在⻚⾯布局中占据空间,会影响其他元素的位置。因此,它们必须参与布局计算 7。
3.1.4 计算样式
对于每⼀个被包含进渲染树的可⻅节点,浏览器会遍历 CSSOM,找到所有匹配的 CSS 规则,并根据层叠、优先级等规则计算出该节点的最终“计算样式”(Computed Style)。这个包含内容和计算后样式的节点,就是渲染树的基本构成单位 6。
3.2 布局 (Layout 或 Reflow)
3.2.1 ⼏何计算阶段
渲染树构建完成后,虽然我们知道了要渲染哪些节点以及它们的样式,但还不知道它们的具体尺⼨和在屏幕上的位置。布局阶段的任务就是计算出渲染树中每个节点在视⼝(viewport)内的精确⼏何信息 3。
3.2.2 全局性过程
布局通常是⼀个全局性的过程。由于⽹⻚布局的流式特性,⼀个元素的尺⼨或位置变化,可能会影响其⽗节点、⼦节点乃⾄后续的所有兄弟节点。例如,改变 <body> 元素的宽度,可能会导致整个⻚⾯的所有元素都需要重新计算其位置和⼤⼩。因此,布局操作的计算成本可能⾮常⾼,尤其是当 DOM 结构复杂时 13。任何后续对⻚⾯部分或整个⽂档的尺⼨和位置的重新计算,通常被称为“重排”(Reflow) 7。
该阶段的输出是每个元素的“盒模型”(Box Model),其中包含了其在⻚⾯上的精确坐标和尺⼨信息。
3.3 绘制 (Paint 或 Repaint)
3.3.1 填充像素
⼀旦节点的⼏何信息被确定,浏览器就可以进⼊绘制阶段,将这些节点的视觉表现(如颜⾊、边框、阴影等)转换为屏幕上的像素。绘制过程会遍历布局树,并为每个节点⽣成⼀系列的绘制指令,这些指令集合被称为“绘制记录”(Paint Records) 9。
3.3.2 绘制记录与堆叠上下⽂
绘制记录类似于给 <canvas> API 的⼀系列命令,例如:“在坐标 (x, y) 处⽤颜⾊ C 绘制⼀个背景矩形”,“在位置 P 处绘制⽂本 T” 9。
绘制并⾮简单地按照 DOM 顺序进⾏。浏览器必须严格遵守 CSS 的堆叠上下⽂(Stacking Context)和 z-index 属性。这意味着,为了正确处理元素的覆盖关系,绘制过程可能会被分到多个不同的层⾯上进⾏,确保元素以正确的从后到前的顺序被绘制出来 9。
3.4 合成 (Compositing):现代 GPU 加速流⽔线
从简单的布局/绘制模型到现代的布局/绘制/合成三阶段模型,是现代浏览器渲染架构中最重要的演进。这⼀变⾰直接借鉴了计算机图形学的原理,如硬件加速和场景图,从根本上改变了 Web UI 的性能特征。
3.4.1 单⼀图层的问题
如果整个⻚⾯被绘制在⼀个单⼀的巨⼤图层上,那么任何微⼩的视觉变化——⽐如⼀个元素的平移动画,或者⼀个 div 的滚动——都将迫使浏览器在 CPU 上重绘⼤⾯积的区域。这不仅效率低下,⽽且极易因为主线程繁忙⽽导致动画卡顿(Jank)。
3.4.2 图层化 (Layering) 与图层树
为了解决这个问题,现代浏览器引⼊了图层的概念。它会将⻚⾯的某些部分提升到独⽴的“合成层”(Compositor Layer)上,从⽽形成⼀个“图层树”(Layer Tree) 9。满⾜特定条件的元素会被⾃动提升为合成层,例如:
- 拥有 3D 或透视变换(transform: translateZ(0); 或 perspective)的元素。
- 使⽤ will-change 属性明确提⽰浏览器即将发⽣变化的元素 18。
<video>、<canvas>等插件内容。- 通过 CSS 动画或过渡(animation/transition)实现 opacity、transform 变化的元素。
3.4.3 光栅化 (Rasterization)
图层树创建完毕后,主线程会将这些图层信息提交给⼀个独⽴的“合成器线程”(CompositorThread)。合成器线程接管了将每个图层转换为屏幕像素的⼯作,这个过程称为“光栅化” 9。光栅化任务通常会被分派给多个“光栅线程”(Raster Threads)并⾏处理,并且在可能的情况下,会利⽤GPU 进⾏硬件加速,将图层的绘制记录转换为 GPU 纹理(位图) 9。为了提⾼效率,合成器线程还会将⼤的图层分割成多个“图块”(Tiles),并优先光栅化视⼝内可⻅的图块 9。
3.4.4 合成 (Compositing)
最后⼀步是合成。合成器线程收集所有已经光栅化完成的图层(现在是 GPU 上的纹理),然后像操作 Photoshop 图层⼀样,将它们按照正确的位置、尺⼨、透明度等属性组合、叠加在⼀起,⽣成最终的屏幕图像 9。
3.4.5 性能优势
这种架构的最⼤优势在于,那些只影响合成属性(主要是 transform 和 opacity)的视觉变化,可以完全由合成器线程独⽴处理,⽆需再触及主线程。这意味着浏览器可以跳过⾼成本的布局和绘制阶段。合成器线程只需要告诉 GPU 移动或改变某个已存在纹理的透明度即可。这个操作在 GPU 上执⾏起来极为⾼效,因此即使主线程正在忙于执⾏复杂的 JavaScript,也能保证动画和滚动的绝对流畅 9。这也解释了现代 CSS 性能优化的核⼼准则:“优先使⽤
transform 和 opacity 进⾏动画”。因为改变 width 或 left 等属性会触发主线程上完整的“布局 → 绘制 → 合成”流⽔线,成本⾼昂,是性能优化的反模式。Chromium 的 RenderingNG 架构就明确地设计了这种可跳过部分阶段的优化流⽔线 19。
第 4 节:性能病理与⾼级优化
本节将从理论转向实践,深⼊探讨因误解渲染流⽔线⽽产⽣的常⻅且严重的性能问题,并详细介绍⽤于规避这些问题的先进技术。
4.1 强制同步布局与布局抖动
这可以说是开发者最常引发,也是最严重的性能问题之⼀。它发⽣在 JavaScript 代码迫使浏览器在其常规的、每帧⼀次的优化渲染周期之外,同步执⾏布局计算时 15。
4.1.1 病因:写后即读 (Read-After-Write)
浏览器为了优化性能,会尝试将所有由 JavaScript 引起的样式变更(写操作)缓存起来,然后在下⼀帧即将绘制之前,进⾏⼀次统⼀的布局计算。然⽽,如果脚本在修改了样式(例如 element.style.width = '500px';)之后,⽴即去查询该元素的⼏何属性(例如 element.offsetHeight),就破坏了这⼀优化。为了返回⼀个准确的值,浏览器别⽆选择,只能⽴即应⽤刚才的样式变更,并强制执⾏⼀次完整的、同步的布局计算 15。这个过程被称为“强制同步布局”(Forced Synchronous Layout)或“强制重排”。
4.1.2 布局抖动 (Layout Thrashing)
当这种“写后即读”的操作在⼀个循环中反复发⽣时,情况会变得灾难性地糟糕。这种现象被称为“布局抖动”(Layout Thrashing)。考虑以下代码:
functionresizeAllParagraphs() {
constbox = document.getElementById('box');
constparagraphs = document.querySelectorAll('p');
for(leti = 0; i < paragraphs.length; i++) {
// 读操作,强制浏览器进⾏布局以获取最新的 box.offsetWidth
// 写操作,使布局失效
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}这段代码的意图是将所有段落的宽度设置为与 box 元素的宽度⼀致。然⽽,在每次循环中,它都⾸先读取 box.offsetWidth(⼀个读操作),然后设置 paragraphs[i].style.width(⼀个写操作)。这导致了⼀个“读-写-读-写”的恶性循环。在每次迭代中,浏览器都必须为了响应读操作⽽强制重排整个⻚⾯,紧接着这个布局⼜因为写操作⽽⽴即失效。这使得布局计算在⼀次 JavaScript 执⾏中被触发了成百上千次,造成了巨⼤的性能浪费 15。
4.1.3 解决⽅案:批量处理读写
避免布局抖动的关键在于,重新组织代码以尊重浏览器的渲染流⽔线。正确的做法是,将读操作和写操作分离,进⾏批量处理:
functionresizeAllParagraphsOptimized() {
constbox = document.getElementById('box');
constparagraphs = document.querySelectorAll('p');
// 1. ⾸先执⾏所有读操作
constwidth = box.offsetWidth;
// 2. 然后在循环中执⾏所有写操作
for(leti = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}通过这种⽅式,所有写操作被缓存起来,浏览器只需要在函数执⾏完毕后的下⼀帧绘制前,执⾏⼀次布局计算即可,从⽽彻底避免了布局抖动 15。
这种现象也揭⽰了 Web 平台中⼀个“泄露的抽象”(Leaky Abstraction)。DOM API 表⾯上提供了⼀个简单的同步对象模型,但其性能表现却与底层异步、流⽔线化的渲染引擎紧密相连。API 的同步特性(如读取 offsetWidth)迫使底层的异步流⽔线停顿,这种底层实现细节“泄露”到 API 性能特征中,是布局抖动的根本原因。这也从⼀个侧⾯解释了为何虚拟 DOM(Virtual DOM)库(如React)如此流⾏:它们通过提供⼀个更具声明性的 API,在开发者和真实 DOM 之间建⽴了⼀个缓冲层,其协调算法能够智能地批量处理 DOM 的读写操作,从⽽有效地为开发者屏蔽了布局抖动的复杂性。
| 类别(Category) | 属性/⽅法(Property/Method) | 描述(Description) |
|---|---|---|
| 元素尺⼨ | el.offsetHeight, el.offsetWidth, el.clientHeight, el.clientWidth | 获取元素的可视⾼度/宽度,包括内边距和边框。 |
| 元素位置 | el.offsetLeft, el.offsetTop, el.clientLeft, el.clientTop | 获取元素相对于其 offsetParent 的左/上偏移量。 |
| 滚动信息 | el.scrollHeight, el.scrollWidth, el.scrollTop, el.scrollLeft | 获取元素的内容总⾼度/宽度,或当前的滚动位置。 |
| ⼏何矩形 | el.getBoundingClientRect(), range.getClientRects() | 获取元素或范围相对于视⼝的位置和尺⼨信息。 |
| 计算样式 | window.getComputedStyle(el) | 获取元素的最终计算样式。在特定条件下会触发强制布局 22。 |
4.2 利⽤合成器实现流畅动画
要实现每秒 60 帧(60fps)的流畅动画,每⼀帧的全部处理时间必须控制在 16.7 毫秒以内。在复杂的应⽤中,要稳定达到这个⽬标,唯⼀可靠的⽅法就是尽可能地避免触发布局和绘制。
4.2.1 合成器专属属性
如第 3 节所述,transform(⽤于平移、缩放、旋转)和 opacity(⽤于淡⼊淡出)是两个特殊的 CSS 属性。对它们进⾏动画处理,通常可以完全由合成器线程接管,从⽽绕过主线程的渲染流⽔线。这意味着动画的执⾏不会受到主线程上 JavaScript 任务的影响,是实现⾼性能动画的⾸选⽅案 9。
4.2.2 will-change 属性
will-change 是⼀个提供给开发者的性能优化“提⽰”。通过设置 will-change: transform, opacity;,开发者可以提前告知浏览器,该元素的 transform 和 opacity 属性即将发⽣变化。收到这个提⽰后,浏览器可以预先进⾏⼀些优化,最常⻅的就是将该元素提升到⼀个独⽴的合成层上,⽽⽆需等到动画开始的那⼀刻。这可以有效避免因动画启动时才进⾏图层提升⽽导致的初始帧卡顿 8。
然⽽,will-change 必须被审慎使⽤。它并⾮解决所有性能问题的灵丹妙药,⽽应被视为解决已知性能瓶颈的最后⼿段。过度使⽤(例如,将其应⽤于⼤量元素)会适得其反,因为创建和维护合成层会消耗⼤量的内存和 GPU 资源。最佳实践是,通过 JavaScript 在动画即将开始时添加该属性,并在动画结束后⽴即移除它 18。
4.3 使⽤开发者⼯具进⾏分析与诊断
现代浏览器提供了强⼤的开发者⼯具,⽤于诊断渲染性能问题 23。Chrome DevTools 的 Performance ⾯板是分析这些问题的利器:
- 识别⻓任务: 在主线程的时间线上,⻓时间运⾏的 JavaScript 任务会显⽰为带有红⾊⻆标的⻩⾊条块。这些⻓任务会阻塞主线程,导致⻚⾯⽆响应。
- 定位强制布局: 当发⽣强制同步布局或布局抖动时,Performance ⾯板会显⽰出紫⾊的 “Recalculate Style”(重新计算样式)块和紧随其后的绿⾊的“Layout”(布局)块。这些通常是性能问题的直接根源 24。
- 分析图层: Layers(图层)⾯板可以可视化⻚⾯的合成层结构,帮助开发者理解哪些元素被提升到了独⽴的图层,以及提升的原因,从⽽诊断与图层相关的问题。
第 5 节:现代渲染引擎的⽐较分析
本节将对当今三⼤主流渲染引擎——Blink、Gecko 和 WebKit——进⾏架构层⾯的深⼊剖析,⽐较它们的设计哲学、进程模型以及对性能和现代 Web 标准的实现⽅法。这场“浏览器引擎之战”已从早期的功能竞赛,演变为关乎架构哲学的深度博弈。三⼤引擎代表了三种不同的解决⽅案,⽤以应对同⼀个挑战:如何在多核、配备 GPU 的现代硬件上,安全、⾼效地渲染复杂多变的现代⽹络内容。
5.1 Blink (Chromium) 与 RenderingNG 架构
5.1.1 概述
Blink 是由 Google 主导开发的开源渲染引擎,于 2013 年从 WebKit 分⽀⽽来。它是 Chromium 项⽬的核⼼部分,因此被⼴泛应⽤于 Google Chrome、Microsoft Edge、Opera 等众多浏览器中 25。其最新的渲染架构被称为 RenderingNG(Rendering Next Generation),旨在通过极致的模块化和进程隔离来提升性能、稳定性和安全性。
5.1.2 进程模型:最⼤化隔离
RenderingNG 采⽤了⾼度粒度的多进程架构,以实现强⼤的安全和稳定性保障 19。
- 浏览器进程 (Browser Process): 负责管理浏览器的主界⾯(地址栏、标签⻚等)、⽹络请求和⽤⼾输⼊。
- 渲染器进程 (Renderer Process): 核⼼组件,负责渲染单个⽹站的内容。为了安全,不同的⽹站通常会被分配到不同的渲染器进程中(站点隔离 Site Isolation)。⼀个渲染器进程的崩溃不会影响到其他标签⻚或浏览器本⾝ 19。
- 可视化进程 (Viz Process): ⼀个专⻔的 GPU 进程,负责聚合来⾃所有渲染器进程和浏览器进程的合成层,并与 GPU 通信进⾏最终的绘制。将 GPU 操作隔离到独⽴进程,有助于防⽌因 GPU 驱动程序崩溃⽽导致整个浏览器崩溃 19。
5.1.3 线程模型:主线程与合成器线程的分离
在每个渲染器进程内部,⼯作被进⼀步划分到不同的线程中:
- 主线程 (Main Thread): 负责执⾏ JavaScript、解析 HTML/CSS、计算样式、布局和⽣成绘制记录 9。
- 合成器线程 (Compositor Thread): 独⽴于主线程,负责处理⽤⼾输⼊(如滚动、触摸)、执⾏合成动画,并协调光栅化任务。这种分离是实现流畅交互的关键,因为它确保了即使⽤⼾主线程被⻓时间任务阻塞,滚动和合成动画依然能够流畅运⾏ 9。
5.1.4 渲染流⽔线
RenderingNG 定义了⼀个⾮常明确、包含多个阶段的渲染流⽔线,如动画、样式、布局、预绘制、绘制、提交、图层化、光栅化、激活、聚合和最终绘制。这个精细的流⽔线设计允许在特定场景下智能地跳过某些阶段。例如,⼀个纯粹的 transform 动画可以完全在合成器线程上完成,从⽽跳过主线程上的布局和绘制阶段,实现极致的性能 19。
5.2 Gecko (Firefox) 与 Quantum 项⽬
5.2.1 概述
Gecko 是 Mozilla 基⾦会开发的开源渲染引擎,以其对 Web 标准的严格遵守⽽闻名,是 Firefox 浏览器的核⼼ 26。为了在多核时代保持竞争⼒,Mozilla 发起了名为“Quantum”的宏⼤项⽬,对 Gecko 的核⼼组件进⾏了现代化改造。
5.2.2 Quantum 项⽬的⽬标与实现
Quantum 的核⼼思想是利⽤ Rust 语⾔的内存安全和并发特性,逐步重写 Gecko 中性能最关键的部分,从⽽在不牺牲稳定性的前提下,充分利⽤现代多核处理器的并⾏计算能⼒ 29。
Stylo (并⾏ CSS 引擎): Quantum 项⽬的第⼀个重⼤成果。它整合了 Mozilla 的实验性引擎 Servo 中的并⾏ CSS 系统,使得 CSS 样式的计算可以被分派到多个 CPU 核⼼上并⾏处理,极⼤地加快了样式计算这⼀关键阶段的速度 28。
WebRender (基于 GPU 的渲染器): 这是 Quantum 项⽬的另⼀个⾥程碑。WebRender 同样源⾃ Servo,它⽤⼀个全新的、从头开始为 GPU 设计的渲染后端取代了 Gecko 旧的图形系统。它的⼯作⽅式更像⼀个游戏引擎,通过在 GPU 上维护⼀个⻚⾯的“场景图”,将绝⼤部分绘制任务都转移到 GPU 上,从⽽显著提升了绘制和合成的性能和流畅度 28。
5.2.3 进程模型:Fission 项⽬
Gecko 同样采⽤了多进程架构。其“Fission”项⽬(意为“裂变”)将其进程模型演进为更安全的“站点隔离”模式,即每个进程只处理来⾃单个⽹站的内容,这与 Chromium 的模型趋同,旨在增强对 Spectre 等侧信道攻击的防御能⼒ 28。
5.3 WebKit (Safari)
5.3.1 概述
WebKit 是由苹果公司主导开发的开源渲染引擎,是 Blink 的前⾝,为所有苹果设备上的 Safari 浏览器提供动⼒ 26。
5.3.2 架构特点:与⽣态系统深度整合
尽管与 Blink 有着共同的起源,但 WebKit 在过去⼗多年⾥已独⽴发展。其最⼤的架构特点是与苹果的操作系统(macOS, iOS, iPadOS)和硬件的深度垂直整合。例如,WebKit 可以直接利⽤操作系统底层的 Core Animation 等框架来实现⾼效的合成与动画,从⽽在苹果设备上实现卓越的性能和能效表现 32。
5.3.3 市场地位
由于苹果 App Store 的政策规定,所有在 iOS 和 iPadOS 上发布的第三⽅浏览器都必须使⽤ WebKit 作为其渲染引擎。这⼀政策赋予了 WebKit 在移动端⼀个独特且稳固的市场份额,但也引发了关于平台开放性和竞争的讨论 33。
5.4 架构综合与关键区别
这三⼤引擎的架构选择反映了其背后开发团队的不同优先考量。Blink 的架构设计优先考虑的是⼤规模应⽤下的安全性和稳定性;Gecko 的 Quantum 项⽬则是⼀次雄⼼勃勃的技术⾰新,旨在通过新语⾔和新范式攻克并⾏计算的难题;⽽ WebKit 则⾛了另⼀条路,通过与特定软硬件⽣态的深度绑定来追求极致的优化。
| 架构⽅⾯(ArchitecturalAspect) | Blink(Chromium) | Gecko(Firefox) | WebKit(Safari) |
|---|---|---|---|
| 主要⽀持者 | Mozilla | Apple | |
| 核⼼浏览器 | Chrome, Edge, Opera | Firefox | Safari |
| 进程模型 | 多进程(渲染器、浏览器、GPU);站点隔离 | 多进程;站点隔离(Fission) | 多进程(UI、Web 内容) |
| JavaScript 引擎 | V8 | SpiderMonkey | JavaScriptCore |
| CSS 引擎 | 标准实现 | Stylo (并⾏化) | 标准实现 |
| 渲染/合成器 | RenderingNG | WebRender / Quantum Compositor | 与 Core Animation 深度整合 |
| 核⼼设计哲学 | 模块化与安全隔离 | 并⾏化与内存安全 | 操作系统与硬件整合 |
这种架构上的多样性,虽然给需要跨平台测试的开发者带来了挑战 34,但对整个 Web ⽣态的健康发展⾄关重要。它避免了技术上的完全单⼀化,并促进了不同创新路径的探索。然⽽,Blink 引擎在市场上的主导地位也带来了对“事实标准”的担忧,即许多⽹站可能只针对 Chromium 进⾏测试和优化,这可能会在未来边缘化其他引擎,损害⽹络的多样性和开放性 33。
结论
从⼀⾏简单的 HTML 代码到⽤⼾屏幕上⼀个完全合成、可交互的动态⻚⾯,浏览器的渲染过程是⼀段复杂⽽精密的旅程。本报告深⼊剖析了这⼀旅程的每⼀个关键节点:从构建 DOM 和 CSSOM 的初始解析,到结合⼆者形成渲染树,再到经历布局、绘制和最终合成的完整流⽔线。
分析表明,现代浏览器的渲染架构已经⾼度演进,其核⼼驱动⼒在于应对⽇益增⻓的 Web 应⽤复杂性、对流畅交互的性能要求以及严峻的安全挑战。关键渲染路径(CRP)的概念为我们理解和优化⾸屏加载提供了基础框架,⽽对强制同步布局和布局抖动等性能病理的认识,则是避免开发常⻅性能陷阱的关键。
更进⼀步,从 CPU 为中⼼的绘制模型到以 GPU 为核⼼的合成模型的转变,是过去⼗年浏览器渲染领域最重要的架构⻜跃。它将动画和滚动等⾼频交互从繁忙的主线程中解放出来,是当今流畅 Web 体验的技术基⽯。对 transform、opacity 和 will-change 等 CSS 属性的正确运⽤,本质上就是开发者在与浏览器的合成器进⾏直接“对话”。
最后,对 Blink、Gecko 和 WebKit 三⼤引擎的⽐较揭⽰了,不存在唯⼀的“最佳”架构,⽽是存在不同设计哲学下的权衡与取舍。Blink 的极致模块化、Gecko 的并⾏化⾰新以及 WebKit 的⽣态系统整合,共同构成了当今多元⽽充满活⼒的浏览器技术格局。
对于当代的 Web 开发者和软件架构师⽽⾔,理解这些底层的渲染原理已不再是⼀项可选的学术追求,⽽是⼀项基础的核⼼能⼒。⽆论是选择前端框架、制定 CSS 编写策略,还是实现复杂的动画效果,每⼀个⾼层次的技术决策都会在渲染流⽔线上产⽣深刻且可预测的连锁反应。掌握这些知识,意味着拥有了做出更明智、更⾼效决策的能⼒,从⽽构建出真正满⾜现代⽤⼾期望的、快速、流畅且稳健的 Web 应⽤。
Works cited
- Web 性能, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Web/Performance
- 了解关键路径 | web.dev, accessed June 25, 2025, https://web.dev/learn/performance/understanding-the-critical-path?hl=zh-cn
- 关键渲染路径- Web 性能| MDN, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Web/Performance/Guides/Critical_rendering_path
- 前端性能优化之关键路径渲染优化· Issue #16 · fi3ework/blog - GitHub, accessed June 25,2025, https://github.com/fi3ework/blog/issues/16
- 分析关键渲染路径性能 | Articles | web.dev, accessed June 25, 2025, https://web.dev/articles/critical-rendering-path/analyzing-crp?hl=zh-cn
- 渲染树构建、布局和绘制| Articles - web.dev, accessed June 25, 2025, https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=zh-cn
- 渲染⻚⾯:浏览器的⼯作原理 - MDN Web Docs, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work
- CSS 性能优化- 学习Web 开发| MDN, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Extensions/Performance/CSS
- 深⼊了解现代⽹络浏览器(第3 部分) | Blog | Chrome for Developers, accessed June 25, 2025, https://developer.chrome.com/blog/inside-browser-part3?hl=zh-cn
- 层叠层- 学习Web 开发| MDN, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Core/Styling_basics/Cascade_layers
- 层叠、优先级与继承- 学习Web 开发| MDN, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts
- display - SVG:可缩放⽮量图形 - MDN Web Docs, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Web/SVG/Reference/Attribute/display
- What is DOM reflow? - html - Stack Overflow, accessed June 25, 2025, https://stackoverflow.com/questions/27637184/what-is-dom-reflow
- Minimizing browser reflow | PageSpeed Insights - Google for Developers, accessed June 25, 2025, https://developers.google.com/speed/docs/insights/browser-reflow
- Avoid large, complex layouts and layout thrashing | Articles - web.dev, accessed June 25, 2025, https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing
- Reflow - Glossary - MDN Web Docs - Mozilla, accessed June 25, 2025, https://developer.mozilla.org/en-US/docs/Glossary/Reflow
- What are Reflow and Repaint? How to optimize? - ExplainThis, accessed June 25, 2025, https://www.explainthis.io/en/swe/repaint-and-reflow
- will-change - CSS:层叠样式表 - MDN Web Docs, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Web/CSS/will-change
- RenderingNG 架构 | Chromium | Chrome for Developers, accessed June 25, 2025, https://developer.chrome.com/docs/chromium/renderingng-architecture?hl=zh-cn
- Layout Thrashing and Forced Reflows - Web Performance Tips, accessed June 25, 2025, https://webperf.tips/tip/layout-thrashing/
- Are layout thrashing, reflow the same meaning in HTML? - Stack Overflow, accessed June 25, 2025, https://stackoverflow.com/questions/66450070/are-layout-thrashing-reflow-the-same-meaning-in-html
- What forces layout/reflow. The comprehensive list. - GitHub Gist, accessed June 25, 2025, https://gist.github.com/paulirish/5d52fb081b3570c81e3a
- Chrome DevTools | Chrome for Developers, accessed June 25, 2025, https://developer.chrome.com/docs/devtools?hl=zh-cn
- Layout thrashing: what is it and how to eliminate it - DEV Community, accessed June 25, 2025, https://dev.to/aayla_secura/layout-thrashing-what-is-it-and-how-to-eliminate-it-n2j
- Blink - MDN Web ⽂档术语表:Web 相关术语的定义, accessed June 25, 2025, https://developer.mozilla.org/zh-CN/docs/Glossary/Blink
- What Are Rendering Engines And How Does They Work - LambdaTest, accessed June 25, 2025, https://www.lambdatest.com/learning-hub/rendering-engines
- 深⼊了解现代⽹络浏览器(第1 部分) | Blog - Chrome for Developers, accessed June 25, 2025, https://developer.chrome.com/blog/inside-browser-part1?hl=zh-cn
- Gecko — Firefox Source Docs documentation, accessed June 25, 2025, https://firefox-source-docs.mozilla.org/overview/gecko.html
- Gecko (software) - Wikipedia, accessed June 25, 2025, https://en.wikipedia.org/wiki/Gecko_(software)
- firefox-source-docs.mozilla.org, accessed June 25, 2025, https://firefox-source-docs.mozilla.org/mobile/android/geckoview/contributor/geckoview-architecture.html#:~:text=Internally%2C Gecko uses a multi,processes also called content processes.
- Comparison of browser engines - Wikipedia, accessed June 25, 2025, https://en.wikipedia.org/wiki/Comparison_of_browser_engines
- Comparative Analysis of Webkit and Non-Webkit Based Browsers and their Future, accessed June 25, 2025, https://www.researchgate.net/publication/378582695_Comparative_Analysis_of_Webkit_and_Non-Webkit_Based_Browsers_and_their_Future/download
- There are basically three rendering engines right now Genially curious, which - Hacker News, accessed June 25, 2025, https://news.ycombinator.com/item?id=38849970
- Behind Browsers: Rendering Engines that Power Your Web Experience - Indulge Media, accessed June 25, 2025, https://indulge.digital/blog/behind-browsers-rendering-engines- power-your-web-experience
- Are There Any Objective Performance Differences Between Blink and Gecko? - Reddit, accessed June 25, 2025, https://www.reddit.com/r/browsers/comments/1jbcyy3/are_there_any_objective_performance_differences/
