0
0
0

网页是怎么到你眼前的:从 HTML 到浏览器渲染

2026-10-16
文章摘要
|

网页是怎么到你眼前的:从 HTML 到浏览器渲染

我在地址栏敲下一个网址、按下回车,不到一秒,一个图文并茂的页面就摊在我眼前。这件事我用了十几年网页,却一直没认真想过中间发生了什么。这篇就把我最近补课的笔记整理出来:从敲下回车到像素出现在屏幕上,浏览器到底经历了哪几步。搞懂这条链路,后面调样式、查性能、排 bug 时才知道自己该站在哪里看问题。

第一步:找到服务器——DNS 与 HTTP 请求

浏览器拿到的只是一个域名,比如 blog.hkycx330.cn,它并不知道我的博客服务器住在哪个机房。所以第一件事是 DNS 解析(Domain Name System,域名系统):把域名翻译成 IP 地址。翻译结果会被缓存好几层——浏览器缓存、操作系统缓存、路由器缓存,层层都命中就直接跳过,这也是为什么第二次打开同一网站明显更快。

拿到 IP 之后,浏览器和服务器建立连接。HTTPS 网站还要先做 TLS 握手(Transport Layer Security,安全传输层协议),交换证书、协商加密算法,来回要几个包。连接建好后,浏览器发出一个 HTTP 请求(HyperText Transfer Protocol,超文本传输协议),服务器返回的第一份文件永远是 HTML——注意,只是一个光秃秃的 HTML 文本,图片、CSS、JS 此刻都还没下载。

可以用命令行亲眼看一眼这个过程,不用写任何代码:

curl -I https://blog.hkycx330.cn

返回里能看到 HTTP/2 200 这样的状态码和一堆响应头(Response Headers),200 就是"内容正常,给你"。

第二步:HTML 变成 DOM 树——浏览器开始读懂结构

拿到 HTML 后,浏览器做的第一件事是解析(Parse):逐字符读 HTML,把一串标签文本转成一颗树状结构,叫 DOM(Document Object Model,文档对象模型)。比如这样一段代码:

<body>
  <h1>渔仔小屋</h1>
  <p>你好,<strong>朋友</strong></p>
</body>

在浏览器眼里不是"三行字",而是一棵树:body 是根,h1 和 p 是它的两个孩子,strong 又是 p 的孩子。后面所有的样式、脚本、交互,都是在这棵树上做文章。

解析过程中如果碰到 <link rel="stylesheet"> 或 <script src="...">,浏览器会同时去下载这些资源(HTTP/2 下可以并发请求多个文件),这就是为什么 Network 面板里能看到几十个请求在时间轴上排开——页面是"边下载边解析边下载"的流水线。

第三步:CSS 与渲染树——只保留看得见的部分

CSS 下载好后,浏览器同样把它解析成一棵树,叫 CSSOM(CSS Object Model,CSS 对象模型)。然后把 DOM 和 CSSOM 合并成一棵渲染树(Render Tree)。

这里有个很多人第一次听说会愣一下的点:渲染树里不包含所有节点。display: none 的元素不会进渲染树(但还在 DOM 里);<head>、<meta> 这种"看不见"的标签也不会。反过来说,opacity: 0 和 visibility: hidden 的元素是看得见的——只是透明或不可见——所以它们在渲染树里,还占位置。这也是为什么"隐藏一个元素"有好几种写法,效果却完全不同:

.a { display: none; }      /* 不占空间,不进渲染树,无法触发过渡动画 */
.b { visibility: hidden; } /* 占空间,不可见 */
.c { opacity: 0; }         /* 占空间,不可见,但还能接收点击 */

每个渲染树节点还会去匹配最终的样式——即"哪个选择器最终说了算"。这就是 CSS 层叠(Cascade)发挥作用的时刻:同优先级后写的覆盖先写的,选择器越具体优先级越高。对新手最实用的一条经验:少用 !important,多用更精确的选择器去解决覆盖问题。

第四步:布局、绘制、合成——像素出现在屏幕上

渲染树有了,但节点们还没有大小和位置。接下来的三步是整个渲染的核心:

  1. 布局(Layout,也叫 Reflow,回流):计算每个节点的宽高和坐标。这一步会从根节点递归往下算,父元素尺寸变了,孩子全要重算。
  2. 绘制(Paint,重绘):把每个节点画成图层数据——文字、边框、颜色、阴影,注意此时只是"画在图层上",还没上屏。
  3. 合成(Composite):把多个图层按正确顺序拼到一起,交给显卡输出到屏幕。

这套流水线对写代码的直接影响是:不同属性引发的代价完全不同。改 width、height、top 会触发布局 → 绘制 → 合成,全流程重来,最贵;改 color、background 只跳过布局,触发绘制;而 transform 和 opacity 通常只需要合成这一步,最便宜。这就是为什么做动画时老手都写 transform: translateX(10px) 而不是 left: 10px——前者走绿色通道,后者每一帧都重新排版。想流畅的动画就盯住 60fps(每帧约 16.7ms 的预算),改布局属性的动画很容易超预算掉帧。

第五步:JavaScript 插队——script 标签的三个姿势

JS 是这条流水线上唯一的"插队者"。默认写法的 <script src="app.js"> 会阻塞 HTML 解析:解析器停下来,等脚本下载并执行完才继续。因为脚本可能修改 DOM,浏览器不敢冒险往前读。

现代写法有两个选择:

<script src="a.js"></script>        <!-- 默认:阻塞,下完立刻执行 -->
<script src="b.js" defer></script>  <!-- 不阻塞,HTML 解析完后按顺序执行 -->
<script src="c.js" async></script>  <!-- 不阻塞,下完立刻执行,顺序不保证 -->

经验法则:独立的小脚本(如统计埋点)用 async;依赖 DOM 结构或彼此有顺序的脚本用 defer;老项目里"把 script 放到 </body> 前"本质上就是在手工模拟 defer。

亲手验证:用 Chrome DevTools 把流程看一遍

以上全是理论,真正消化它要靠 DevTools(开发者工具,Mac 上按 Cmd + Option + I)。我做了一个小小的观察流程:

  1. 看请求流水线:切到 Network 面板,刷新页面。第一行通常是 HTML 文档,后面的 CSS/JS/图片按依赖和时间排开。把鼠标悬在瀑布图的色块上,能看到 Waiting(等服务器响应)、Content Download(下载)各花了多久——首次打开慢,多半慢在这里。
  2. 看渲染树:在 Elements 面板选中一个元素,右侧 Styles 里能看到最终生效的所有规则,被划掉的就是被层叠淘汰的。想确认某个样式为什么没生效,先来这里看谁把它覆盖了。
  3. 看渲染流水线:Performance 面板点录制 → 刷新 → 停止。时间轴上能看到 Parse HTML、Recalculate Style、Layout、Paint、Composite Layers 一个个色块。滚动时如果掉帧,录一段就能看见是哪个环节超时了。
  4. 看渲染树里有没有它:Elements 里能搜到某元素、页面上却看不见?检查它是不是 display: none,或者父容器高度塌陷了。

常见坑排查:页面图片加载时布局乱跳,是图片没写宽高、布局阶段无法预留空间导致的,给 img 加上 width/height 或用 CSS 的 aspect-ratio 就能治;改了个样式没反应,先去 Styles 面板确认是不是被更高优先级规则覆盖,而不是怀疑浏览器抽风。

🛠 本篇要用到的软件

  • Chrome DevTools:本篇的主角。用来查看网络请求瀑布图(Network)、检查渲染树与层叠样式(Elements + Styles)、录制渲染流水线(Performance)。内置于 Google Chrome,支持 macOS / Windows / Linux,完全免费。免费替代:Firefox DevTools(Firefox 内置,功能同样完整)、Safari Web Inspector(macOS 自带,调 Safari 专属问题很好用)。
  • curl:命令行工具,用来发一个最简 HTTP 请求、看响应头,验证 DNS 与状态码。macOS / Linux 自带,Windows 10+ 也内置了,免费。免费替代:Postman(图形界面更友好,有免费版)。

下一篇预告

下一篇:网页设计的三大版图:视觉设计、交互设计、前端实现

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论

欢迎来到我的博客!