返回文章目录

从输入 URL 到页面可交互:浏览器渲染全链路

把一次页面访问拆成网络、解析、样式计算、布局、绘制与交互阶段,建立定位前端性能问题的共同语言。

内容说明本文为个人学习与教学示例,不代表真实生产项目。涉及生产配置时请结合环境评估并完成审查。

打开一个网页看似只是“请求并显示”,实际上浏览器要完成导航、网络、解析、样式计算、布局、绘制、合成以及脚本执行等一连串工作。理解这条链路的价值,不是记住术语,而是知道页面慢时应该观察哪一段证据。

问题背景:页面为什么不是一次画出来的

浏览器拿到的是连续到达的字节流,而不是一张完整图片。HTML 可能先到,样式表稍后到,脚本又可能继续修改文档。浏览器必须一边接收、一边解析,并在“尽快显示”和“避免反复重算”之间取舍。

常见的三个时间点也并不相同:看到首屏内容、主要内容完成、页面可以流畅响应输入。一个页面可以很早出现骨架,却因为主线程仍在执行长任务而无法点击;也可能已经可操作,但大图稍后加载导致布局跳动。

导航与网络请求

导航开始后,浏览器会解析 URL、检查缓存与 Service Worker,再进行 DNS 查询、建立连接并发送 HTTP 请求。HTTPS 还需要完成 TLS 协商。连接能否复用、服务端何时返回首字节、关键资源是否需要额外往返,都会影响后续渲染何时启动。

text
01地址解析 → 缓存检查 → DNS → TCP/QUIC → TLS → HTTP 请求 → 响应字节流

HTML 中发现的 CSS、JavaScript、字体和图片会形成新的请求。浏览器有预加载扫描器,但它无法消除错误的资源优先级。首屏大图、关键样式和普通分析脚本不应拥有相同优先级。

图中 HTML 是后续资源发现的入口。CSS 决定样式,JavaScript 可能读取或修改 DOM,而图片与字体会影响最终布局和绘制。

解析与渲染树

HTML 解析器把标记转换成 DOM。CSS 解析器把样式规则转换成 CSSOM。渲染树由可见 DOM 节点和它们的计算样式组成;display: none 的元素不会进入渲染树,但仍存在于 DOM。

外部样式通常会阻塞首次渲染,因为浏览器不能在不知道样式的情况下可靠绘制页面。普通同步脚本则可能暂停 HTML 解析:脚本可能调用 document.write,也可能查询前面节点的样式。defer 脚本在文档解析完成后按顺序执行,通常更适合应用入口;async 脚本下载完成即执行,顺序不确定,适合彼此独立的脚本。

html
01<link rel="preload" href="/hero.webp" as="image" fetchpriority="high" />
02<script src="/app.js" defer></script>

预加载只应用于确实关键且很快会使用的资源。滥用会挤占带宽,反而拖慢 CSS 或字体。

布局、绘制与合成

样式计算确定每个节点的最终属性;布局根据盒模型、字体和视口计算几何位置;绘制把背景、文字、边框等转成绘制指令;合成线程再把不同图层组合到屏幕。

修改不同属性的成本不同。改变 width 往往需要重新布局并绘制;改变 color 通常只需绘制;对独立合成层做 transform 或 opacity 动画,常常可以避免主线程布局。但“提升为图层”会占用显存,不能把所有元素都加上 will-change。

css
01.moving-card {
02 transform: translate3d(var(--x), 0, 0);
03 transition: transform 180ms ease;
04}
05
06@media (prefers-reduced-motion: reduce) {
07 .moving-card { transition: none; }
08}

可交互不等于视觉完成

DOMContentLoaded 表示 HTML 已解析且延迟脚本执行完成;load 还会等待图片等依赖。它们都不能直接代表真实用户体验。现代性能分析更关注:

  • LCP:主要内容何时完成绘制;
  • INP:用户交互到下一帧呈现的延迟;
  • CLS:生命周期内意外布局偏移的程度。

如果 JavaScript 在主线程连续运行 300 毫秒,即使按钮已经绘制出来,输入也只能排队。解决方法不是添加加载动画,而是减少工作、拆分长任务、延后非关键逻辑,并验证拆分后是否真的改善 INP。

实践:减少关键渲染路径

下面是一组可执行的基础策略:

  1. 服务端尽早返回有意义 HTML,而不是只返回空容器。
  2. 将首屏必要样式与非关键样式区分,删除未使用 CSS。
  3. 应用脚本使用 defer 或模块脚本,第三方脚本延后。
  4. 为图片声明尺寸,避免加载后推开正文。
  5. 对首屏图片使用合适格式、尺寸和优先级。
  6. 用 Performance 面板和真实用户数据验证,而不是凭感觉优化。
ts
01const observer = new PerformanceObserver((list) => {
02 for (const entry of list.getEntries()) {
03 console.log(entry.entryType, entry.name, Math.round(entry.startTime));
04 }
05});
06
07observer.observe({ type: "largest-contentful-paint", buffered: true });

这段代码适合学习和本地诊断。正式采集时要控制采样、清理可能包含业务信息的资源名称,并把指标与页面版本、设备环境关联。

常见误区与总结

“DOM 少就一定快”忽略了样式复杂度、资源体积和脚本成本;“使用 GPU 就一定快”忽略了图层内存与合成成本;“开发机很流畅”也不能代表低端移动设备。

可靠的方法是先把问题放到网络、解析、布局、绘制或主线程任务中的某一段,再用瀑布图、Performance 记录和 Web Vitals 验证。延伸阅读可以继续进入 JavaScript 事件循环、真实用户监控和图片加载策略。

下一篇 设计一套清晰可演进的 REST API