互联网工作原理(五):网页渲染流程

本文将详细解析浏览器如何将HTML、CSS和JavaScript代码转换为可视化的网页页面,深入探讨从接收数据到屏幕显示的完整渲染流程。

什么是网页渲染?

网页渲染是浏览器将HTML、CSS和JavaScript等网络资源转换为可视化页面的过程。当用户在地址栏输入URL并按下回车后,浏览器会获取网页代码,并经过一系列精密处理,最终在屏幕上呈现完整的网页界面。


渲染流程概述

浏览器的渲染流程可以概括为多个连续阶段,形成一条严密的渲染流水线

  1. 解析HTML:构建DOM(文档对象模型)树
  2. 解析CSS:构建CSSOM(CSS对象模型)树
  3. 样式计算:将CSSOM与DOM结合,计算每个节点的最终样式
  4. 布局:生成布局树,计算节点的大小和位置
  5. 分层:将布局树分成多个图层
  6. 绘制:为每个图层生成绘制指令
  7. 分块:将图层划分为更小的区域
  8. 光栅化:将图形信息转换为像素点
  9. 合成与显示:将所有图层合成为最终图像并显示在屏幕上

以下流程图展示了这一完整过程:

浏览器的渲染流程

详细渲染阶段解析

1. 解析HTML构建DOM树

当浏览器接收到HTML文档后,渲染主线程会启动HTML解析过程,将HTML文本转换为DOM(文档对象模型) 树结构。

DOM树构建过程

  • 词法分析:将HTML文本拆分为最小的语法单元(Token)
  • 语法分析:根据Token间的嵌套关系构建节点树
  • DOM树形成:所有节点按照父子关系组成树形结构

示例:简单HTML页面的DOM构建

<!DOCTYPE html>
<html>
<head>
    <title>简单网页示例</title>
</head>
<body>
    <h1>欢迎来到找找网</h1>
    <div class="content">
        <p>这是一个段落文本</p>
        <img src="example.jpg" alt="示例图片">
    </div>
</body>
</html>

上述代码会被解析成如下DOM树结构:

  • html
  • head
    • title
    • “简单网页示例”
  • body
    • h1
    • “欢迎来到找找网”
    • div (class=”content”)
    • p
      • “这是一个段落文本”
    • img (src=”example.jpg”, alt=”示例图片”)

2. 解析CSS构建CSSOM树

在构建DOM树的同时,浏览器会解析所有CSS样式(包括外部CSS文件、内部样式和行内样式),构建CSSOM(CSS对象模型) 树。

CSSOM构建特点

  • 解析CSS选择器和声明块
  • 确定样式规则的层叠顺序和优先级
  • 形成包含所有样式信息的树形结构

示例:CSS样式解析

<!DOCTYPE html>
<html>
<head>
    <title>CSS解析示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
        }

        h1 {
            color: #3366cc;
            border-bottom: 1px solid #eee;
        }

        .content {
            background-color: #f5f5f5;
            padding: 15px;
        }
    </style>
</head>
<body>
    <h1>CSS解析示例</h1>
    <div class="content">
        <p>这是一个带有样式的段落。</p>
    </div>
</body>
</html>

3. 样式计算

样式计算阶段,浏览器会将DOM树和CSSOM树结合,为每个DOM节点计算出最终的样式。

样式计算过程

  • 将CSS规则应用到对应的DOM节点上
  • 处理样式继承和层叠规则
  • 将相对值转换为绝对值(如em转为px,颜色名称转为RGB值)

4. 布局阶段

布局阶段(也称为”重排”或”回流”)会计算渲染树中每个节点的几何信息,生成布局树

布局过程

  • 遍历DOM树中的可见节点
  • 计算每个节点的位置、大小、边距等几何信息
  • 形成布局树(注意:DOM树与布局树不一定一一对应)

示例:布局计算

<!DOCTYPE html>
<html>
<head>
    <title>布局计算示例</title>
    <style>
        .container {
            width: 800px;
            margin: 0 auto;
        }

        .sidebar {
            width: 200px;
            height: 400px;
            float: left;
            background: #e0e0e0;
        }

        .main-content {
            width: 600px;
            height: 400px;
            float: left;
            background: #f0f0f0;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="sidebar">侧边栏</div>
        <div class="main-content">主内容区</div>
    </div>
</body>
</html>

注意:以下情况的DOM节点不会包含在布局树中:

  • 使用display: none的元素(不占据空间)
  • <head>标签内的元素
  • 注释节点

而以下情况则会包含在布局树中:

  • 使用visibility: hidden的元素(占据空间但不可见)
  • 使用opacity: 0的元素(占据空间但不可见)
  • 伪元素(如::before::after

5. 分层与绘制

分层:为了提高渲染效率,浏览器会使用复杂策略将布局树分成多个图层。以下属性会影响分层结果:

  • position不为static且设置了z-index
  • opacity值不为1
  • transform值不为none
  • filter值不为none
  • will-change属性

绘制:为每个图层生成绘制指令集,描述如何绘制该层的内容。完成绘制后,主线程将绘制信息提交给合成线程,剩余工作由合成线程完成。

6. 分块与光栅化

分块:合成线程将每个图层划分为更小的区域(分块),优先处理视口附近的块。

光栅化:将每个块转换为位图(像素数据)。这一过程会使用GPU加速,提高处理速度。

7. 合成与显示

合成:合成线程计算出每个位图在屏幕上的位置,生成指引(quad)信息。

显示:合成线程将quad提交给GPU进程,由GPU硬件完成最终的屏幕成像。


重排与重绘

在网页交互过程中,当页面结构或样式发生变化时,浏览器需要重新渲染部分或全部内容,这就会触发重排和重绘。

重排(Reflow)

重排是当元素的布局属性发生变化时,浏览器重新计算元素的几何属性,并重新构建布局树的过程。

触发重排的操作

  • 添加或删除可见的DOM元素
  • 元素位置、大小、内容改变
  • 页面渲染初始化
  • 浏览器窗口大小改变
  • 读取某些布局属性(如offsetWidth、offsetHeight等)

重绘(Repaint)

重绘是当元素的可见样式发生变化但不影响布局时,浏览器重新绘制元素外观的过程。

触发重绘的操作

  • 颜色改变
  • 背景色改变
  • 边框颜色改变
  • 透明度改变
  • 可见性改变

重要关系:重排一定会引起重绘,但重绘不一定触发重排。

重排与重绘对比

特性重排重绘
触发原因布局变化样式变化(不影响布局)
性能成本
影响阶段布局及之后所有阶段绘制及之后所有阶段
触发属性width、height、position等color、background、visibility等

性能优化建议

理解渲染流程后,可以采取以下优化策略提高网页性能:

1. 减少重排和重绘

优化策略

  • 使用CSS3 transform和opacity属性实现动画(这些属性只触发合成阶段,不触发重排和重绘)
  • 批量读写DOM样式,减少布局抖动
  • 避免在循环中频繁操作DOM

示例:使用transform优化动画

<!DOCTYPE html>
<html>
<head>
    <title>动画优化示例</title>
    <style>
        .ball {
            width: 100px;
            height: 100px;
            background: #f40;
            border-radius: 50%;
        }

        /* 使用transform实现的动画 - 高性能 */
        .ball-optimized {
            animation: move-optimized 1s alternate infinite ease-in-out;
        }

        /* 使用left实现的动画 - 低性能 */
        .ball-normal {
            position: absolute;
            left: 0;
            animation: move-normal 1s alternate infinite ease-in-out;
        }

        @keyframes move-optimized {
            to {
                transform: translate(100px);
            }
        }

        @keyframes move-normal {
            to {
                left: 100px;
            }
        }
    </style>
</head>
<body>
    <p>使用transform实现的动画(高性能):</p>
    <div class="ball ball-optimized"></div>

    <p>使用left实现的动画(低性能):</p>
    <div class="ball ball-normal"></div>
</body>
</html>

2. HTML解析优化

优化策略

  • CSS放在文档头部:确保浏览器尽早解析CSS,避免渲染阻塞
  • 合理使用JavaScript的async和defer属性
  • 减少DOM节点数量,降低渲染树复杂度

3. CSS优化

优化策略

  • 使用简单的CSS选择器
  • 减少CSS嵌套层级
  • 避免使用CSS表达式
  • 使用Flexbox或Grid布局,它们比传统布局更具性能优势

完整示例:理解渲染流程的简单页面

下面是一个完整的HTML页面示例,通过这个示例可以观察不同渲染阶段的效果:

<!DOCTYPE html>
<html>
<head>
    <title>网页渲染流程演示</title>
    <style>
        /* 初始样式 */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            transition: all 0.3s;
        }

        .container {
            width: 80%;
            margin: 0 auto;
            padding: 20px;
            border: 1px solid #ddd;
            background-color: #f9f9f9;
        }

        .box {
            width: 100px;
            height: 100px;
            background: #4CAF50;
            margin: 10px;
            display: inline-block;
        }

        .hidden {
            display: none;
        }

        /* 触发重排的样式 */
        .wide {
            width: 200px;
        }

        /* 触发重绘的样式 */
        .colored {
            background: #2196F3;
        }

        /* 只触发合成的样式 */
        .transformed {
            transform: scale(1.2);
        }

        button {
            margin: 5px;
            padding: 8px 15px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>网页渲染流程演示</h1>
        <p>通过此演示页面,可以观察不同操作对渲染流程的影响:</p>

        <div>
            <button onclick="toggleBox()">切换显示/隐藏(触发重排)</button>
            <button onclick="changeColor()">改变颜色(触发重绘)</button>
            <button onclick="transformBox()">变换大小(触发合成)</button>
            <button onclick="changeSize()">改变尺寸(触发重排+重绘)</button>
        </div>

        <div id="boxContainer">
            <div class="box" id="demoBox"></div>
        </div>

        <div id="infoPanel">
            <h3>渲染阶段指示:</h3>
            <p id="renderInfo">当前状态:初始渲染完成</p>
        </div>
    </div>

    <script>
        const demoBox = document.getElementById('demoBox');
        const renderInfo = document.getElementById('renderInfo');
        let isVisible = true;
        let isColored = false;
        let isTransformed = false;
        let isWide = false;

        function toggleBox() {
            isVisible = !isVisible;
            if (isVisible) {
                demoBox.classList.remove('hidden');
                renderInfo.textContent = "操作:显示元素(触发重排+重绘)";
            } else {
                demoBox.classList.add('hidden');
                renderInfo.textContent = "操作:隐藏元素(触发重排+重绘)";
            }
        }

        function changeColor() {
            isColored = !isColored;
            if (isColored) {
                demoBox.classList.add('colored');
                renderInfo.textContent = "操作:改变颜色(触发重绘)";
            } else {
                demoBox.classList.remove('colored');
                renderInfo.textContent = "操作:恢复颜色(触发重绘)";
            }
        }

        function transformBox() {
            isTransformed = !isTransformed;
            if (isTransformed) {
                demoBox.classList.add('transformed');
                renderInfo.textContent = "操作:变换大小(触发合成,性能最佳)";
            } else {
                demoBox.classList.remove('transformed');
                renderInfo.textContent = "操作:恢复大小(触发合成,性能最佳)";
            }
        }

        function changeSize() {
            isWide = !isWide;
            if (isWide) {
                demoBox.classList.add('wide');
                renderInfo.textContent = "操作:改变尺寸(触发重排+重绘)";
            } else {
                demoBox.classList.remove('wide');
                renderInfo.textContent = "操作:恢复尺寸(触发重排+重绘)";
            }
        }
    </script>
</body>
</html>

兼容性与常见问题

常见渲染问题及解决方案

问题现象可能原因解决方案
页面内容闪烁CSS加载过慢,导致重绘将CSS放在文档头部
布局突然变化图片未设置尺寸为图片设置width和height属性
动画卡顿使用了触发重排的属性使用transform和opacity实现动画
滚动性能差过多图层或复杂样式减少图层数量,优化CSS

开发注意事项

  1. CSS编写
  • 避免使用过于复杂的选择器
  • 减少CSS表达式和滤镜的使用
  • 使用Flexbox/Grid布局替代传统浮动布局
  1. JavaScript优化
  • 使用requestAnimationFrame替代setTimeout实现动画
  • 批量DOM操作,减少重排次数
  • 使用事件委托减少事件处理程序数量
  1. 资源加载
  • 关键CSS内联到HTML中
  • 非关键CSS和JavaScript使用异步加载
  • 图片懒加载,减少初始渲染压力

教程知识点总结

知识点内容描述
网页渲染定义浏览器将HTML、CSS和JavaScript转换为可视化页面的过程
DOM树构建浏览器解析HTML,构建文档对象模型树状结构
CSSOM树构建浏览器解析CSS,构建CSS对象模型树
样式计算将CSSOM与DOM结合,计算每个节点的最终样式
布局阶段计算每个节点的几何信息,生成布局树
分层将布局树分割为多个图层,提高渲染效率
绘制为每个图层生成绘制指令集
分块将图层划分为更小的区域
光栅化将图形信息转换为像素点
合成与显示将所有图层合成为最终图像并显示
重排重新计算布局树,性能开销大
重绘重新计算绘制指令,性能开销中等
性能优化减少重排重绘、优化CSS和JavaScript、使用transform和opacity实现动画