渲染流程(上):HTML、CSS和JavaScript是如何变成页面的|浏览器篇
# 渲染流程(上):HTML、CSS和JavaScript是如何变成页面的
30 秒速记
- 浏览器接收
HTML、CSS和JavaScript,经过渲染模块处理后输出屏幕像素。 HTML提供标签语义与文本内容,CSS通过选择器和属性决定展示样式,JavaScript可以修改内容或样式。- 渲染流水线按文中所述依次涉及构建
DOM树、样式计算、布局、分层、绘制、分块、光栅化和合成。 - 理解每个子阶段时,应分别说明输入、处理过程和输出,避免只背阶段名称。
- 掌握流水线可以帮助解释开发者工具指标、页面卡顿、动画优化以及强制同步布局等问题;具体内部实现应以对应 Chrome 版本为准。
浏览器会把 HTML、CSS 和 JavaScript 交给渲染模块处理,最终转换成屏幕上的像素。 简单来说,HTML 提供结构和内容,CSS 决定样式,JavaScript 则可以动态修改两者。中间流程可归纳为构建结构与计算样式、确定布局、生成绘制内容,再完成栅格化和合成,因为这几类阶段分别解决“画什么、画在哪、怎么显示”。理解时要关注每个阶段的输入、处理和输出,这比只背阶段名称更有助于排查卡顿、动画和强制同步布局问题;内部细节仍要结合具体 Chrome 版本判断。
在上一篇文章中我们介绍了导航相关的流程,那导航被提交后又会怎么样呢?就进入了渲染阶段。这个阶段很重要,了解其相关流程能让你“看透”页面是如何工作的,有了这些知识,你可以解决一系列相关的问题,比如能熟练使用开发者工具,因为能够理解开发者工具里面大部分项目的含义,能优化页面卡顿问题,使用JavaScript优化动画流程,通过优化样式表来防止强制同步布局,等等。
既然它的功能这么强大,那么今天,我们就来好好聊聊渲染流程。
通常,我们编写好HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面(如下图所示),但是你知道它们是如何转化成页面的吗?这背后的原理,估计很多人都答不上来。

从图中可以看出,左边输入的是HTML、CSS、JavaScript数据,这些数据经过中间渲染模块的处理,最终输出为屏幕上的像素。
这中间的渲染模块就是我们今天要讨论的主题。为了能更好地理解下文,你可以先结合下图快速抓住HTML、CSS和JavaScript的含义:

从上图可以看出,HTML的内容是由标记和文本组成。标记也称为标签,每个标签都有它自己的语意,浏览器会根据标签的语意来正确展示HTML内容。比如上面的<p>标签是告诉浏览器在这里的内容需要创建一个新段落,中间的文本就是段落中需要显示的内容
如果需要改变HTML的字体颜色、大小等信息,就需要用到CSS。CSS又称为层叠样式表,是由选择器和属性组成,比如图中的p选择器,它会把HTML里面<p>标签的内容选择出来,然后再把选择器的属性值应用到<p>标签内容上。选择器里面有个color属性,它的值是red,这是告诉渲染引擎把<p>标签的内容显示为红色
至于JavaScript(简称为JS),使用它可以使网页的内容“动”起来,比如上图中,可以通过JavaScript来修改CSS样式值,从而达到修改文本颜色的目的。
搞清楚HTML、CSS和JavaScript的含义后,那么接下来我们就正式开始分析渲染模块了。
由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的HTML经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线,其大致流程如下图所示:

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建DOM树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。内容比较多,我会用两篇文章来为你详细讲解这各个子阶段。接下来,在介绍每个阶段的过程中,你应该重点关注以下三点内容
- 开始每个子阶段都有其输入的内容;
- 然后每个子阶段有其处理过程;
- 最终每个子阶段会生成输出内容。
理解了这三部分内容,能让你更加清晰地理解每个子阶段。
面试官追问
追问 1线上商品标题文字正确,但颜色和字号都错了,同事据此认定 HTML 解析失败;你会怎样反驳并划定排查范围?
仅凭样式错误不能认定 HTML 解析失败,因为 HTML 主要提供标记、语义和文本,颜色与字号由 CSS 规则决定。应先确认目标节点存在,再检查选择器、属性以及 JavaScript 是否动态改写样式;后续仍需结合实际渲染阶段定位。
追问 2你让候选人解释首屏从文件到像素的过程,他只背出“构建 DOM、布局、绘制”,在工程评审里还缺少哪些关键信息?
还缺少样式计算、分层、分块、光栅化与合成等流水线关系,也没有交代各阶段的输入、处理和输出。工程判断不能只靠阶段名称,因为定位卡顿时需要知道数据在哪一步发生变化;具体耗时仍须借助开发者工具验证。
追问 3一个静态活动页后来加入 JavaScript 动态改色和动画,负责人认为输入文件没变多,原有渲染分析无需调整,你怎么判断?
需要调整,因为 JavaScript 能修改内容或 CSS 样式值,运行时变化可能让渲染流水线再次处理相关数据。应把脚本触发的状态变化纳入观察范围,而不能只分析初始 HTML 和样式表;仅凭材料还不能断定具体哪个阶段最慢。
追问 4线上页面滚动时卡顿,开发者工具里同时出现布局、绘制和合成活动,值班同学直接把问题归因于 CSS 文件过大,你会怎样排查?
不能仅凭多个渲染阶段出现就归因于 CSS 文件大小,应先对齐卡顿时间段并查看各阶段的次数和耗时。再结合脚本是否修改样式、页面是否发生几何或视觉变化缩小范围;没有运行数据时,流水线知识只能指导定位,不能替代证据。
追问 5团队准备优化动画,一方只盯 JavaScript 执行时间,另一方只盯最终合成;你会如何建立更完整的分析链路?
应沿着输入到像素的流水线检查脚本修改了什么,以及它如何影响样式计算、布局、分层、绘制、分块、光栅化和合成。每一阶段都要明确输入与输出,再用录制数据确认实际瓶颈;不能因某个阶段理论上存在,就假定它必然是主要成本。
# 构建DOM树
30 秒速记
- 浏览器不会直接以HTML文本驱动页面操作,而是由HTML解析器将其转换成内存中的DOM树。
- DOM以节点和父子关系组织文档内容,为后续样式计算以及JavaScript访问提供结构基础。
- DOM与HTML源码含义接近,但前者是运行时对象,可通过
document查询和修改。 - JavaScript修改DOM节点后,页面内容可以随之变化;此时节点只有文档结构,最终外观还要经过样式计算。
构建 DOM 树,就是把浏览器不能直接操作的 HTML 文本,解析成内存中的树状结构。 树中的节点通过父子关系组织页面内容,既为后续样式计算提供结构,也让 JavaScript 能通过 document 查询和修改节点。比如修改某个 p 节点的 innerText 后,页面内容会随之变化。不过此时只确定了文档结构,节点最终长什么样还要经过样式计算。
为什么要构建DOM树呢?这是因为浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器能够理解的结构——DOM树。
这里我们还需要简单介绍下什么是树结构,为了更直观地理解,你可以参考下面我画的几个树结构:

从图中可以看出,树这种结构非常像我们现实生活中的“树”,其中每个点我们称为节点,相连的节点称为父子节点。树结构在浏览器中的应用还是比较多的,比如下面我们要介绍的渲染流程,就在频繁地使用树结构。
接下来咱们还是言归正传,来看看DOM树的构建过程,你可以参考下图

从图中可以看出,构建DOM树的输入内容是一个非常简单的HTML文件,然后经由HTML解析器解析,最终输出树状结构的DOM。
为了更加直观地理解DOM树,你可以打开Chrome的“开发者工具”,选择“Console”标签来打开控制台,然后在控制台里面输入“document”后回车,这样你就能看到一个完整的DOM树结构,如下图所示:

图中的document就是DOM结构,你可以看到,DOM和HTML内容几乎是一样的,但是和HTML不同的是,DOM是保存在内存中树状结构,可以通过JavaScript来查询或修改其内容。
那下面就来看看如何通过JavaScript来修改DOM的内容,在控制台中输入:
document.getElementsByTagName("p")[0].innerText = "black"
这行代码的作用是把第一个<p>标签的内容修改为black,具体执行结果你可以参考下图:

从图中可以看出,在执行了一段修改第一个<p>标签的JavaScript代码后,DOM的第一个p节点的内容成功被修改,同时页面中的内容也被修改了
好了,现在我们已经生成DOM树了,但是DOM节点的样式我们依然不知道,要让DOM节点拥有正确的样式,这就需要样式计算了
面试官追问
追问 1运营在控制台执行 document.getElementsByTagName("p")[0].innerText = "black" 后首段立即变化,他说服务器上的 HTML 文件也被改了,你怎么判断?
被修改的是浏览器内存中的运行时 DOM 节点,不是服务器保存的原始 HTML 文件。页面会依据新的节点内容呈现变化,但刷新后是否保留取决于资源或应用逻辑是否再次提供该值,不能把当前页面状态等同于源码持久化。
追问 2一个内容页有上千个节点,埋点脚本要修改首个 p 的文本;从构建 DOM 的职责看,这段脚本依赖了什么能力?
脚本依赖 DOM 提供的树状、可查询和可修改结构,通过节点 API 定位首个 p 并更新其内容。浏览器先把 HTML 解析为内存中的 DOM,脚本才能执行这类操作;节点更新后的后续渲染成本不由本节材料单独决定。
追问 3服务端返回的 HTML 文本完全相同,但新版页面增加脚本,在加载后重写了多个节点;你还能把抓到的响应正文当作当前页面结构吗?
不能,响应正文只是构建初始 DOM 的输入,脚本可在运行时继续查询和修改节点。排查时应同时检查网络响应与开发者工具中的当前 document;两者不一致并不必然表示解析错误,也可能是正常的脚本更新。
追问 4线上按钮能看到文字,却始终没有预期颜色,同事在控制台确认 DOM 节点存在后就停止排查,你会继续看哪里?
节点存在只证明 HTML 已形成可用的 DOM 结构,不能证明它已获得正确样式。应继续检查后续样式计算中目标节点匹配了哪些规则及最终计算值;若结构和样式都正确,再沿后续渲染阶段结合工具观察。
追问 5评审中有人建议跳过 DOM,让业务脚本直接修改原始 HTML 字符串以更新页面;这种方案为什么不符合当前运行模型?
浏览器需要先把无法直接使用的 HTML 文本解析成可操作的 DOM 树,业务脚本通常查询和修改的是这份内存结构。修改一段脱离文档的字符串不会自动改变当前页面;若重新解析并替换结构,还会引入额外更新范围和状态管理成本。
