浏览器渲染页面底层解析💝

335 阅读7分钟

1 浏览器是如何解析 html, css , js 的?

浏览器底层是 cpp 写的,cpp 是多线程执行的,所以浏览器渲染页面的全过程是多线程的。来看看这张图:

这就是浏览器从资源接收开始,逐步拆解 HTML -> 可视化页面的完整渲染过程。

2 浏览器渲染页面全过程

2.1 资源加载:从网络到浏览器

  1. 网络请求

    1. 浏览器通过 HTTP/HTTPS 等协议,从服务器获取 HTML、CSS、JS、图片 等资源。
    2. 资源会被缓存,优先缓存加载已存在的文件。
  2. 资源解析优先级

    1. HTML:作为 “骨架”,是浏览器解析的起点,阻塞后续渲染(除非异步加载)。
    2. CSS:作为 “皮肤”,会阻塞渲染(需构建 CSSOM 才能计算样式),但可并行加载(多个 CSS 文件同时下载)。
    3. JS:作为 “交互逻辑”,会阻塞 HTML 解析需执行完 JS 才继续解析),可通过 async/defer 优化。

2.2 核心流程:HTML / CSS -> DOM / CSSOM 树 -> 渲染树 -> 可视化

2.2.1 构建 DOM 树(HTML → DOM Tree)

  • 输入:HTML 字符串(网络传输后转成字符流)。

  • 过程:

    • 字节转换:将网络传输的二进制数据转为 UTF-8 等编码的字符。

    • 令牌化(Tokenization):按 HTML 语法拆分字符为令牌(如标签名 <div>、属性 id="box"、文本内容等)。

    • 构建节点:将令牌转换为 DOM 节点对象(包含 typeattrschildren 等属性)。

    • 生成树结构:按嵌套关系拼接节点,形成DOM 树(根节点为 document),便于用数据结构进行树结构的查找等操作(bfs dfs)。

    •   Eg:

    •     <div id="box">Hello</div>
      
    •   令牌化:

    •     {
              type: 'div',
              attrs: { id: 'box' },
              children: [ { type: 'text', content: 'Hello' } ]
          }
      
    •    2.2.2 构建 CSSOM 树(CSS → CSSOM Tree)
    • 输入:CSS 规则(内联样式、<style>、外部 CSS 文件)。

    • 过程:

      • 解析 CSS 规则:拆分选择器(如 div#box)、属性(如 color: red)。
      • 计算样式优先级:处理层叠(Cascade)、继承(Inheritance)、 specificity(权重),确定每个元素的最终样式。
      • 生成 CSSOM 树:按选择器层级组织,形成CSS 对象模型(类似 DOM 树的结构,但只存储样式规则,还没渲染到页面)。
    • 关键特点:

      • CSSOM 是渲染阻塞资源:必须等 CSSOM 构建完成,才能进入 “布局” 阶段(否则无法计算元素位置)。

      • 浏览器会自动补全默认样式(如 body { margin: 8px })。

2.2.3 构建渲染树(Render Tree)

  • 输入:DOM 树 + CSSOM 树。

  • 过程:

    • 筛选可见节点:过滤 display: none<head> 中的元数据等不可见元素
    • 关联样式:为每个可见 DOM 节点匹配 CSSOM 中的样式规则,生成渲染节点(包含样式、位置等信息)。
    • 生成渲染树:按可见节点的层级关系,拼接成渲染树(Render Tree)。
  • 示例:

    • DOM 树中有 <div id="box">,CSSOM 中有 #box { color: red },则渲染树中该节点会携带 color: red 样式。

2.2.4 布局(Layout):计算元素位置

  • 输入:渲染树。

  • 过程

    • 建立坐标系:以视口(Viewport)为基准,确定根节点的位置。
    • 计算盒模型:遍历渲染树,计算每个节点的 widthheightmarginpadding 等(基于 CSS 盒模型)。
    • 处理文档流:按 displayblock/inline/flex 等)规则,确定元素在页面中的坐标位置(如 lefttop)。
    • 处理 BFC(块格式化上下文):解决浮动、 margin 折叠等问题,保证布局稳定。
  • 关键触发条件

    • 首次渲染、窗口 resize、元素样式修改(如 width 变化)会触发回流(Reflow),成本较高(需重新计算整个布局)。

2.2.5 分层(Layer):优化渲染性能

  • 作用:将复杂页面拆分为多个图层,避免全局重绘 / 回流。

  • 分层条件

    • 元素含 transformopacityposition: fixed/absolutez-index 等属性。
    • 视频、Canvas、WebGL 等独立内容。
  • 原理: 浏览器为每个图层单独处理绘制和合成,利用 GPU加速(减少 CPU 压力)。

2.2.6 绘制(Painting):填充像素

  • 输入:分层后的渲染树。

  • 过程

    • 生成绘制指令:遍历每个图层,将样式(颜色、边框、阴影、文字等)转换为像素绘制指令。
    • 光栅化(Rasterization):将矢量图形(如 CSS 样式)转换为位图(像素网格),适配屏幕分辨率。
    • 处理合成层:不同图层的绘制结果会被暂存,等待最终合并。
  • 关键触发条件: 修改不影响布局的样式(如 colorbackground)会触发重绘(Repaint),成本低于回流

2.2.7 合成(Compositing):合并图层

  • 输入:各图层的绘制结果。

  • 过程

    • 确定层叠顺序:按 z-index 等规则,确定图层的上下层级。

    • 合成最终图像:通过合成线程将所有图层合并为一张完整的页面图像,输出到屏幕。

2.2.8 显示(Display):输出到屏幕

  • 合成后的图像被提交到显示器,通过显卡、屏幕驱动等硬件,最终呈现为用户可见的页面。

3 动画与渲染流程的关联

3.1 CSS 动画(transition/animation)

  • 触发时机:样式变化(如 lefttransform)被检测到后,浏览器自动在渲染流程中插入 “过渡帧”。

  • 原理:

    • 浏览器在布局 / 绘制阶段,自动计算关键帧之间的样式插值(如从 left: 0left: 300px 的中间值)。
    • 利用 GPU 加速(尤其是 transform/opacity,仅触发合成层更新,跳过回流 / 重绘)。
  • 案例:我想实现这个红色盒子box 从 left:0; 移动到 left:300px; 的动画效果。

<style>
    .box{
        width: 100px;
        height: 100px;
        background-color: red;
        position: relative;
        left: 0;
        top: 0;
        transition: left 1s ease-out;/*需要检测到两个不同时间点的样式状态才触发*/
    }

    .active{
        left: 300px;
    }
</style>

<div class="box"></div>
<script>
    document.querySelector('.box').classList.add('active');//刷新页面直接显示最后left:300px的样子。
</script>

这个结果并没有我想象的动画效果,why??

原因:在 DOM 树和 CSSOM树 构建完毕之后,.box的初始样式(left:0)已生效,但还没有对页面进行渲染。执行到了JS(JS 代码在 DOM 解析完成后立即执行(因为 导致浏览器批量处理样式变化,跳过了 “过渡帧” 检测。而过渡动画需要检测到两个不同时间点的样式状态,但批量处理导致这两个状态被合并为一次更新。

3.2 JS 动画(requestAnimationFrame)

  • 原理: requestAnimationFrame(RAF)会在浏览器下一次重绘前执行回调。
  • 案例:我想实现这个红色盒子box 从 left:0; 移动到 left:300px; 的动画效果。
 <style>
    .box{
        width: 100px;
        height: 100px;
        background-color: red;
        position: relative;
        left: 0;
        top: 0; 
    }
</style>

<div class="box"></div>
<script>
    const box = document.querySelector('.box');
    let width = 0;
    function move(){
        width += 2;
        box.style.width = width + 'px';
        if (width < 300) {
            requestAnimationFrame(move); //  requestAnimationFrame是浏览器提供的动画API
        }
    }
    move();
</script>

成功实现。

4 关键事件与渲染时机

4.1 DOMContentLoaded

  • 触发时机:DOM 树构建完成后(无需等待 CSS、图片加载)。
  • 特点:此时渲染流程尚未完成(布局、绘制可能还在进行),但可操作 DOM。

4.2 load

  • 触发时机:所有资源(CSS、JS、图片等)加载完成后。
  • 特点:页面已完成首次渲染,可获取最终布局信息(如 offsetWidth)。

4.3 requestAnimationFrame

  • 触发时机:下一次重绘前。

  • 作用:保证动画帧与渲染流程对齐,避免丢帧。

5 回流与重构

5.1 回流

  • 定义:当浏览器发现元素的几何属性(位置、尺寸等)发生变化,或者触发了某些会影响布局的操作时,需要重新计算元素在文档流中的位置和大小,以及对其他元素的影响,然后再重新构建渲染树、重新布局页面,这个过程被称为回流。

  • 触发条件:修改元素尺寸、改变元素位置、添加或删除可见的DOM元素、修改字体大小、改变浏览器窗口大小、激活CSS伪类(:hover:active等)。

  • 对性能的影响:回流的开销比重绘大很多,因为它涉及到重新计算元素的位置和大小,以及对整个页面布局的重新构建。

5.2 重构

  • 定义:当元素的外观(样式)发生改变,但不会影响其在文档流中的位置和布局(几何属性)时,浏览器会将新样式应用到该元素上,并重新绘制它,这个过程被称为重绘。
  • 触发条件:改变元素的颜色、修改背景属性、更改边框样式、改变阴影。
  • 对性能的影响:虽然重绘会消耗一定的性能,因为浏览器需要重新计算元素的样式并绘制到屏幕上,但相对回流而言,重绘的开销较小。

6 总结:从输入到输出的完整链路图