从输入 URL 到首屏:关键渲染路径(CRP)在浏览器里做了什么?
知识背景
用户在地址栏输入 URL 或点击链接后,浏览器要把 HTML、CSS、JS 变成像素画在屏幕上。这条链路叫关键渲染路径(Critical Rendering Path):它决定了首屏有多快、为何会「白屏」「闪屏」「布局抖动」。
前端优化里的「减少阻塞」「内联关键 CSS」「defer/async」「预连接」等,几乎都围绕 CRP 的某一环。Node 不直接参与渲染,但SSR(服务端渲染)会把「生成 HTML 的时机」提前到服务器,改变的是浏览器收到的第一份文档,同样影响 CRP。
知识详解与通俗解释
1. 总体流水线(简化版)
可以记五个大字:建 DOM → 建 CSSOM → 合样式树 → 布局 → 绘制(与合成)。
- 解析 HTML → DOM
遇到<script>(无defer/async)可能阻塞 HTML 解析(经典「脚本在文档中间会卡住后面 DOM 构建」)。 - 解析 CSS → CSSOM
CSS 默认阻塞渲染(不阻塞 HTML 解析,但会挡住「合样式树→布局→绘制」的后续步骤,直到关键 CSS 可用)。 - 合并成渲染树(Render Tree)
只包含需要显示的节点(如display:none一般不进),带最终计算样式。 - 布局(Layout / Reflow)
算几何:元素占多大、在什么坐标。改宽度、改字体等常触发重新布局。 - 绘制(Paint)与合成(Composite)
分层、画位图、GPU 合成到屏幕。改transform/opacity等有时可走合成层,减少大面积重绘(性能优化常提)。
通俗说:DOM 是「结构图纸」,CSSOM 是「装修方案」,合起来才知道每面墙刷什么色;布局是「量房放家具」,绘制是「真正动笔画」。
2. JavaScript 为什么常被认为是性能杀手?
- JS 能改 DOM 和 CSSOM(
element.style、classList),因此可能触发重新样式计算、布局、绘制。 - 执行时间过长会霸占主线程,拖长首次渲染或可交互时间(TTI 相关指标变差)。
- 因此有:脚本放底部、
defer/async、拆包、Web Worker等策略。
3. defer 与 async(和首屏关系很大)
- 无属性:下载可并行,但执行会阻塞 HTML 解析(除非
type="module"等特殊情况,行为以浏览器为准)。 async:下载并行,下载完尽快执行,执行顺序与文档顺序无关,适合独立脚本(如统计)。defer:下载并行,在 HTML 解析完成后、按文档顺序执行;适合依赖 DOM 的业务脚本。
记忆口诀:defer 排队守秩序,async 谁先下好谁先跑。
4. 与 SSR / Node 的一点联系
- CSR(纯客户端渲染):首屏常常先拿到空壳 HTML + 大 JS,白屏时间取决于 JS 下载与执行。
- SSR:首包 HTML 里已有内容,更快看到字,但 hydration 仍要吃 JS;Node 在这里负责更早把「可绘制的结构」送出去。
总结
- CRP 是 DOM + CSSOM → 渲染树 → 布局 → 绘制/合成;任一环节变慢都会拖累首屏。
- CSS 阻塞渲染、JS 可能阻塞解析并触发重排重绘——优化要针对「关键资源」下刀。
defer/async、减少主线程长任务、优先保证关键 CSS,是日常最实用的抓手。- 理解 CRP 后,再看 Lighthouse与 Performance 面板里的 Main thread、Parse、Layout、Paint,会顺很多。