HTML/CSS/JS 是如何渲染页面的

36 阅读7分钟

HTML/CSS/JS 是如何变成页面的?

作为前端开发者,你每天都在写 HTML、CSS、JS,但你是否想过:这些纯文本代码,是如何被浏览器转化为可视化页面的?为什么有时候修改 CSS 会卡顿?为什么 JS 执行会阻塞渲染? 本文会从 Chrome 浏览器的底层渲染流程出发,拆解 HTML/CSS/JS 渲染页面的完整链路,结合 DOM 树、CSSOM 树、渲染树、回流重绘等核心概念,不仅讲清楚 “怎么渲染”,还会结合语义化、性能优化等实战知识点,让你既懂原理,又会落地。

一、先搞懂:浏览器渲染的核心目标

浏览器接收 HTML/CSS/JS 等文本输入,最终输出 “可视化页面”,核心目标是:

  • 把无序的文本代码,转化为结构化的树状模型;
  • 快速、流畅地绘制像素到屏幕(理想状态下 1 秒绘制 60 次,即 60fps,保证视觉流畅);
  • 尽可能降低渲染开销,避免卡顿(这也是性能优化的核心)。

Chrome 浏览器的渲染工作主要由渲染引擎(Blink) 负责,整个流程看似复杂,实则有明确的执行顺序 —— 理解这个顺序,是前端性能优化的基础。

二、浏览器渲染页面的完整流程(核心)

从输入 HTML/CSS/JS 到输出页面,Chrome 会经历以下 6 个核心阶段,缺一不可:

image.png

下面我们逐阶段拆解,讲清楚每个步骤的核心工作和实战意义。

2.1 第一步:解析 HTML → 构建 DOM 树

浏览器无法直接处理 HTML 字符串,必须先将其转化为结构化的 DOM 树(Document Object Model) —— 这是页面渲染的 “骨架”。 核心过程:

  1. 字节 → 字符:浏览器先将下载的 HTML 字节(如 UTF-8 编码)转化为可读字符;
  2. 字符 → 标记:解析字符,识别 HTML 标签、属性、文本等标记;
  3. 标记 → 节点:为每个标记创建 “节点对象”(包含标签名、属性、文本、父子关系等);
  4. 节点 → DOM 树:按 HTML 的嵌套关系,递归构建树状结构(根节点是document)。

举个例子:

<!DOCTYPE html> 
  <html>
   <body> 
       <h1>Hello</h1>
       <p>World</p>
   </body>
  </html>

构建后的 DOM 树简化结构:

document 
└── html
     └── body 
          ├── h1 (文本:Hello) 
          └── p (文本:World)

实战意义:HTML 语义化的核心价值 DOM 树的结构完全依赖 HTML 的书写方式,语义化的 HTML 不仅让 DOM 树更清晰,还有两大核心作用:

  • SEO 优化:搜索引擎爬虫(如百度蜘蛛)会解析 DOM 树,语义化标签(header/main/footer/section等)能让爬虫快速识别页面核心内容,提升搜索排名;
  • 性能与体验:
    1. 语义化标签(如h1-h6)自带默认的 DOM 结构优先级,主内容用main、侧边栏用aside,浏览器会优先解析 / 加载主内容;
    2. 配合 CSS 的flex: order(如order: -1让main优先渲染),可进一步优化内容加载顺序。

👉 开发建议:避免用div通吃所有布局,优先使用header/main/footer/nav/article等语义化标签,ul>li代替纯div实现列表,h1-h6按层级使用(一个页面仅一个h1)。

2.2 第二步:解析 CSS → 构建 CSSOM 树

和 HTML 一样,浏览器也无法直接处理 CSS 字符串,需要转化为CSSOM 树(CSS Object Model) —— 这是页面渲染的 “样式规则集”。 核心过程:

  1. 解析 CSS 文本,识别选择器(如div、.box、#id)、属性(如color、font-size)和值;
  2. 计算样式优先级(!important > 内联样式 > ID 选择器 > 类选择器 > 标签选择器 > 继承);
  3. 构建树状结构的 CSSOM,每个节点对应 DOM 节点的样式规则(包含继承的样式)。

举个例子:

body { font-size: 16px; } 
h1 { color: red; font-size: 2em; }

构建后的 CSSOM 树简化结构:

CSSOM 
├── body 
│     └── font-size: 16px
└── h1 
     ├── color: red 
     ├── font-size: 32px (2em = 2*16px) 
     └── 继承自body:font-size(被自身覆盖)

关键特性:CSSOM 树是 “阻塞渲染” 的 浏览器必须等待所有 CSS 解析完成并构建 CSSOM 树后,才能进入下一步 —— 因为 CSS 样式会影响节点的布局和绘制,未解析完 CSS 就渲染,会导致页面 “闪屏”(先显示无样式内容,再加载样式)。

2.3 第三步:结合 DOM+CSSOM → 构建渲染树(Render Tree)

渲染树是 DOM 树和 CSSOM 树的 “结合体”,但并非简单拼接,核心规则:

  1. 只包含可见的 DOM 节点(隐藏节点如display: none、标签、meta标签等不会加入渲染树);
  2. 为每个可见节点绑定对应的 CSSOM 样式规则(继承 + 自身样式);
  3. 保持 DOM 树的层级结构,确保样式能正确继承。 举个例子(延续上文 DOM/CSSOM): 渲染树结构:
    Render Tree
    ├── body (font-size: 16px)
        ├── h1 (color: red, font-size: 32px)
        └── p (font-size: 16px, 继承自body)

区别:visibility: hidden的节点会加入渲染树(占据空间但不可见),display: none的节点不会加入(不占据空间,完全隐藏)。

2.4 第四步:布局(Layout / 重排):计算节点的位置和大小

渲染树构建完成后,浏览器会执行 “布局” 阶段(也叫 “重排 / 回流”):

  1. 从根节点开始,递归计算每个节点的盒模型属性:宽度、高度、margin、padding、border、位置(top/left 等);
  2. 结合视口(viewport)大小,确定每个节点在屏幕上的精确坐标;
  3. 输出 “布局流”(Layout Flow),记录所有节点的几何信息。 触发布局的常见操作:
  • 修改 DOM 节点的尺寸(width/height)、位置(top/left);
  • 增删 DOM 节点;
  • 修改窗口大小(resize);
  • 修改字体大小、padding/margin;
  • 调用offsetWidth、scrollTop等获取布局属性的 API(会强制浏览器提前布局)。

2.5 第五步:绘制(Paint / 重绘):填充像素

布局完成后,浏览器进入 “绘制” 阶段:

  1. 按渲染树的层级,为每个节点绘制像素内容(颜色、背景、边框、阴影、文本等);
  2. 绘制操作会被拆分为多个 “绘制层”(如文字层、背景层),提升绘制效率;
  3. 输出 “绘制列表”(Paint List),记录每个绘制层的绘制指令。 触发绘制的常见操作:
  • 修改节点的颜色、背景、阴影、边框样式(不改变尺寸 / 位置);
  • 注意:绘制不会改变节点的几何信息,开销比布局小。

2.6 第六步:合成(Composite):分层渲染到屏幕

这是渲染的最后一步,浏览器会将所有绘制层合并为最终的页面,并展示到屏幕上:

  1. 将绘制层按层级叠加(如 z-index 高的层覆盖低的层);
  2. 利用 GPU 加速合成(GPU 擅长处理分层渲染);
  3. 将合成后的图像发送到显示器,完成最终渲染(理想状态下 60fps,即每 16.6ms 刷新一次)。 性能优化关键:优先触发合成,避免布局 / 绘制 修改transform(如translate)、opacity等属性,只会触发 “合成” 阶段(开销最小),不会触发布局 / 绘制 —— 这是移动端动画优化的核心技巧。

三、JS 如何影响页面渲染?

JS 是 “动态修改页面” 的核心,但它也是渲染流程中最容易 “阻塞” 的环节,核心原因:

3.1 JS 的执行会阻塞 DOM 解析

浏览器解析 HTML 时,若遇到

  1. 下载 JS 文件(若为外部脚本);
  2. 执行 JS 代码(JS 可修改 DOM/CSSOM,如document.createElement、element.style.color);
  3. 执行完成后,恢复 DOM 解析。 为什么要阻塞? JS 可能会修改已解析的 DOM 节点,甚至重写整个文档(如document.write),若不阻塞 DOM 解析,会导致 JS 操作的节点还未被解析,引发错误。

3.2 JS 可修改 CSSOM,导致重新布局 / 绘制

JS 不仅能修改 DOM,还能修改 CSS 样式(如element.style.fontSize = '20px'),会触发:

  1. CSSOM 重新计算;
  2. 渲染树重新构建;
  3. 布局(重排)→ 绘制(重绘)→ 合成(视情况)。

3.3 优化 JS 渲染阻塞的方案

  • 使用async/defer: 1.async:异步下载 JS,下载完成后立即执行(不保证顺序,不阻塞 DOM 解析); 2.defer:异步下载 JS,DOM 解析完成后执行(保证顺序,适合依赖 DOM 的脚本);
  • 将 JS 放在底部:先完成 DOM 解析,再执行 JS,避免阻塞首屏渲染;
  • 避免频繁修改 DOM/CSS:批量修改样式(如用class代替直接修改style),减少布局 / 绘制次数;
  • 使用requestAnimationFrame:将 DOM/CSS 修改放在浏览器重绘前执行,避免多次布局。