浏览器渲染原理

浏览器渲染原理是前端性能优化的理论基础,理解浏览器如何将HTML、CSS和JavaScript转换为用户可见的网页,对于编写高性能的前端代码至关重要。本文将详细讲解浏览器渲染的完整流程。


一、渲染流程

1.1 资源的请求过程

静态资源(HTML、CSS、JavaScript、图片等)通过网络请求从服务器获取,然后由浏览器进行解析和渲染,最终呈献给用户。

html 复制代码
静态资源 → 浏览器(网络下载)→ 解析处理 → 网页

1.2 浏览器加载资源的过程

浏览器渲染页面是一个流水线过程,主要包括以下步骤:

  1. HTML解析:解析HTML文档,构建DOM树
  2. CSS解析:解析CSS样式,构建CSSOM树
  3. 渲染树构建:将DOM树和CSSOM树结合,形成渲染树
  4. 布局计算:计算每个节点的位置和尺寸
  5. 页面绘制:将渲染树绘制到屏幕上
  6. 合成渲染:将各层合成最终画面
(1)HTML解析器的作用?

HTML解析器(HTML Parser)的作用是解析HTML文档,将各个HTML标签转化为DOM节点,从而构成DOM树。DOM(Document Object Model)树是浏览器内部表示文档结构的数据结构,它将文档视为一个树形结构,每个HTML标签、文本、属性都是树中的一个节点。

(2)CSS解析器的作用?

CSS解析器(CSS Parser)的作用是解析CSS样式表(外部CSS文件、内联样式、style标签),将CSS规则转换为浏览器内部可识别的数据结构,构建CSSOM树。CSSOM(CSS Object Model)与DOM类似,是一种树形结构,存储了所有CSS样式信息。

(3)渲染树什么时候构建?

当DOM树和CSSOM树都构建完成后,才开始进行渲染树的构建。也就是说,必须等到HTML解析完成(DOM树就绪)并且CSS解析完成(CSSOM树就绪)后,浏览器才会将两者结合生成渲染树(Render Tree)。

这意味着:

  • 如果CSS尚未加载完成,渲染树无法构建,页面将处于空白状态
  • 这也是为什么CSS通常放在HTML头部(<head>)加载的原因------尽早开始下载CSS
(4)计算布局是基于什么?

计算布局(Layout)基于渲染树(Render Tree)。渲染树包含了所有需要显示的DOM节点及其计算后的样式信息,浏览器根据这些信息来计算每个元素的几何属性(位置、大小)。

布局计算会考虑:

  • 元素的盒模型(content、padding、border、margin)
  • 元素的定位方式(static、relative、absolute、fixed)
  • 浮动元素
  • 文档流(Normal Flow)
(5)计算布局基于什么算法?

计算布局主要基于盒模型算法CSS布局系统

  • 流式布局(Flow Layout):块级元素从上到下,行内元素从左到右
  • Flexbox(弹性盒布局):一维布局模型
  • Grid(网格布局):二维布局模型
  • 定位布局:基于top、right、bottom、left属性

对于复杂的嵌套布局,浏览器会采用回流(Reflow)算法自上而下或自下而上地计算每个元素的位置。

(6)页面何时渲染?

页面在绘制(Paint)阶段完成后就会渲染。具体流程是:

  1. 布局计算完成 → 生成布局信息
  2. 绘制指令生成 → 创建绘制任务
  3. 合成线程处理 → 分层、光栅化
  4. 最终合成 → 推送到显卡,显示器显示

注意:首次渲染发生在DOM树和CSSOM树结合后,布局和绘制完成时。

(7)思考与总结
  • 陈述HTML渲染成网页元素的核心流程?

    HTML渲染的核心流程是:下载HTML → HTML解析器构建DOM树 → CSS解析器构建CSSOM树 → 结合DOM和CSSOM构建渲染树 → 计算布局 → 绘制页面 → 合成显示。这是一个流水线式的过程,每个阶段依赖于前一个阶段的完成。

  • 哪些环节容易造成性能问题,为什么?

    容易造成性能问题的环节包括:

    • JavaScript执行:如果JS脚本阻塞DOM解析,会延迟页面渲染
    • CSS加载:CSS未加载完成会阻塞渲染树构建,导致白屏
    • 频繁的DOM操作:每次DOM变化都可能触发重排或重绘
    • 复杂的布局计算:深层嵌套的Flex/Grid布局会增加计算量
    • 大量的重绘重排:特别是同步的、频繁的样式变化
  • 计算布局的主要目的是什么?

    计算布局的主要目的是确定每个渲染树节点在视口中的确切位置和尺寸。只有知道每个元素应该在哪里、有多大,浏览器才能正确地将页面绘制到屏幕上。

  • 绘制是由计算机的哪个硬件完成的?

    绘制主要由**GPU(图形处理器)**完成。现代浏览器的合成层会在GPU上光栅化处理,显卡负责将最终的像素数据推送到显示器。


二、DOM树的构建

2.1 示例代码

假设当前有一段这样的HTML代码:

html 复制代码
<html>
  <link rel="stylesheet" href='/a.css'>
  <body>
    <p>hello world</p>
    <script src='/a.js'></script>
  </body>
</html>

2.2 转化成DOM树

2.3 分析转化为DOM树的过程

DOM树的构建过程如下:

  1. 字节流 → 分词器:网络接收的HTML字节流被转换为令牌(Token)
  2. 令牌流 → 解析器:分词器生成的令牌被解析器处理
  3. 构建DOM树:解析器根据令牌创建DOM节点,逐步构建树形结构

2.4 HTML字符串到Token流

HTML字符串首先被**分词器(Tokenizer)**拆分为各种类型的Token:

  • 开始标签(如<html><body><p>
  • 结束标签(如</html></body></p>
  • 文本内容
  • 注释

2.5 Token流到DOM树

解析器接收Token流,通过栈结构来维护DOM树的层级关系:

  • 遇到开始标签时,创建节点并入栈
  • 遇到结束标签时,当前节点出栈
  • 这样逐步构建出完整的DOM树

2.6 思考与总结

  • 陈述DOM树的构建过程?

    DOM树的构建过程是:浏览器首先通过网络下载HTML文档,然后将HTML字符串分词(生成Token流),接着解析器逐个处理Token,根据Token类型创建相应的DOM节点,最后通过栈结构维护节点间的父子关系,逐步构建出树形结构。

  • 如果遇到script节点,之前的css link在没解析完之前是否会阻塞JavaScript执行?

    。这是因为JavaScript可能会查询或修改DOM和CSSOM。为了保证JavaScript执行时能够获取到正确的样式信息,浏览器会等待CSSOM构建完成后再执行JavaScript。因此,如果CSS未加载完成,不仅渲染树无法构建,JavaScript的执行也会被阻塞。

  • tokens流的作用是什么?

    Token流的作用是将原始的HTML字符串转换为结构化的标记数据,使得解析器能够更容易地处理和识别HTML的各个组成部分。Token是HTML文档结构的最小单位,解析器通过识别Token类型(开始标签、结束标签、文本等)来构建DOM树。


三、CSSOM的构建

3.1 DOM解析和CSSOM解析会阻塞吗?

DOM解析和CSSOM解析会相互阻塞

  1. DOM解析不阻塞CSSOM解析:HTML解析器可以继续构建DOM树,同时CSS解析器并行构建CSSOM树
  2. CSSOM解析阻塞JS执行:JavaScript需要等待CSSOM构建完成才能执行(因为JS可能读取计算后的样式)
  3. CSSOM解析阻塞渲染:必须等到DOM和CSSOM都就绪,才能构建渲染树

这种设计确保了当JavaScript执行时,所有必要的样式信息都已经可用。

3.2 CSSOM数据结构如何?

CSSOM是一个树形结构

  • 根节点表示文档
  • 派生节点表示各CSS规则
  • 每个规则包含选择器声明块(属性-值对)

CSSOM与DOM结构类似,都采用树形组织,但关注点不同:DOM关注文档结构,CSSOM关注样式规则。

3.3 样式的标准化过程

样式标准化(Style Computation)是将各种CSS写法转换为浏览器内部统一格式的过程:

  1. 解析CSS规则:将选择器和声明转换为内部数据结构
  2. 层叠(Cascade):根据来源(内联、内嵌、外链)和优先级处理冲突
  3. 继承(Inheritance):将可继承的属性传递给子元素
  4. 计算值(Computed Value) :将相对值转换为绝对值(如empx
  5. 应用值:最终应用到元素

StyleMap vs computedStyleMap

  • StyleMap:表示元素的内联样式
  • computedStyleMap:表示元素的最终计算样式(包括继承和层叠结果)

3.4 样式的继承(哪些样式可以继承?哪些不能继承?)

可以继承的样式(常见)

可继承属性主要是文本相关字体相关的属性:

  • color
  • font-familyfont-sizefont-weightfont-style
  • line-height
  • text-aligntext-indenttext-transform
  • visibility
  • white-space
  • letter-spacingword-spacing
不能继承的样式

不可继承属性主要是盒模型布局相关的属性:

  • widthheight
  • marginpaddingborder
  • display
  • position
  • background
  • overflow
  • opacity(但在CSS变量中可以继承)

如何强制继承父级样式?

使用inherit关键字:color: inherit;

3.5 综合案例

示例代码
html 复制代码
<html>
  <head>
    <style>
      body {
        color: #fff;
      }
      .header {
        width: 100px;
        height: 50px;
        background: red;
      }
      .content {
        font-size: 25px;
        line-height: 25px;
        margin: 10px;
      }
    </style>
  </head>
  <body>
    <div class="header"></div>

    <p style="color: blue" class="content">
      <span>hello world</span>
      <p style="display: none;">哈哈</p>
    </p>
  </body>
</html>
树状结构

这个案例展示了:

  • 内联样式(style="color: blue")优先级高于class样式
  • display: none的元素不会出现在渲染树中
  • 文本颜色会继承(<span>会继承<p>的蓝色)

3.6 思考与总结

  • DOM树和CSSOM树是同步解析还是串行解析?

    并行解析 。DOM解析和CSSOM解析是并行进行 的,两者互不阻塞。但渲染树的构建需要等待两者都完成后才能开始。

  • 如何强制继承父级样式?

    使用CSS的inherit关键字。例如:

    css 复制代码
    .child {
      color: inherit;  /* 强制继承父元素的color */
      font-size: inherit;
    }
  • 样式的标准化主要干了什么事情,如何验证?

    样式标准化主要干了三件事:

    1. 层叠:处理多个样式来源的冲突
    2. 继承:将可继承属性向下传递
    3. 计算:将相对值转换为绝对值

    验证方法:在浏览器开发者工具的Computed面板中,可以看到每个CSS属性的最终计算值。


四、渲染树的构建

4.1 什么时候开始进行渲染树的构建?

当DOM树和CSSOM树都构建完成后,才开始进行渲染树的构建

渲染树构建的必要条件:

  1. DOM树解析完成
  2. CSSOM树解析完成

只有两者都就绪,浏览器才能将DOM节点与对应的CSS样式关联起来,形成渲染树。

4.2 DOM树和CSSOM树是如何结合的?

渲染树的构建过程:

  1. 遍历DOM树中的每个可见节点
  2. 在CSSOM中查找匹配的CSS规则
  3. 为每个可见节点创建渲染对象(Render Object)
  4. 将渲染对象组织成树形结构

4.3 为什么要构建渲染树?

构建渲染树的目的是将DOM树的语义结构与CSSOM的样式信息结合起来,形成浏览器可以直接用于布局和绘制的树形结构

渲染树的作用:

  • 过滤不可见节点(如<head><script>display: none的元素)
  • 为每个可见节点关联计算后的样式
  • 为后续的布局和绘制提供数据基础

4.4 多个样式作用于一个DOM元素,优先级如何确定?

当多个样式规则作用于同一元素时,CSS通过**层叠(Cascade)优先级(Specificity)**来确定最终样式:

优先级计算规则(从高到低)
  1. 内联样式style="...")--- 权重:1000
  2. ID选择器#id)--- 权重:100
  3. 类选择器.class)、属性选择器[attr])、伪类:hover)--- 权重:10
  4. 元素选择器div)、伪元素::before)--- 权重:1
  5. 通配符*)、组合符>+~)--- 权重:0
层叠顺序(当优先级相同时)
  1. 开发者样式(Author)> 用户样式(User)> 浏览器默认样式(User Agent)
  2. 同等优先级下,后出现的样式覆盖先出现的样式
  3. !important会覆盖正常优先级

4.5 思考与总结

  • 渲染树是如何构建的?

    渲染树的构建过程是:DOM解析完成后,CSSOM也解析完成,然后浏览器从DOM树的根节点开始遍历,对每个可见节点,在CSSOM中查找匹配的样式规则,为其创建渲染对象,最后将这些渲染对象组织成渲染树。

  • 哪些节点不会出现在渲染树中,举例说明?

    不会出现在渲染树中的节点包括:

    • <head>及其子元素(不可见)
    • <script>(不可见)
    • <style><link>(不可见)
    • display: none的元素(不可见)
    • <meta><link>等元数据元素
  • 样式优先级是如何定义的?

    样式优先级由选择器的 specificity(特异性)决定。计算方式为:内联样式权重1000,ID选择器权重100,类/属性/伪类权重10,元素/伪元素权重1。权重高的样式优先生效,权重相同则后定义的生效。


五、页面布局

5.1 页面布局主要完成哪些工作?

页面布局(Layout)主要完成以下工作:

  1. 计算元素尺寸:根据内容、padding、border、margin计算元素实际占用空间
  2. 确定元素位置:根据文档流、定位、浮动确定元素的坐标
  3. 处理嵌套关系:计算父子元素的尺寸传递
  4. 处理响应式:根据视口大小调整布局

布局的输入是渲染树 ,输出是带有几何信息的渲染树(每个节点都有了确切的位置和尺寸)。

5.2 计算布局时采用了哪种算法?

浏览器布局主要采用以下算法:

  1. 盒模型算法:计算每个元素的content、padding、border、margin尺寸
  2. 流式布局算法:块级元素垂直排列,行内元素水平排列
  3. Flexbox算法:一维弹性布局
  4. Grid算法:二维网格布局
  5. BFC(块级格式化上下文)算法:处理浮动元素的布局

对于复杂的嵌套布局,浏览器采用回流算法自上而下或自下而上地计算。


六、页面绘制

6.1 绘制阶段的工作内容是什么?

绘制阶段(Painting)的工作是将渲染树转换为屏幕上的像素。具体包括:

  1. 生成绘制指令:为每个渲染对象生成绘制命令(如"填充矩形"、"绘制文字")
  2. 分层处理:将页面划分为多个合成层
  3. 光栅化:将矢量图形转换为位图像素
  4. 上传GPU:将位图数据上传到显卡

6.2 哪些操作会导致分层?

以下操作会导致浏览器为元素创建独立的合成层

  1. 3D变换transform: translate3d()rotate3d()
  2. 视频<canvas>元素
  3. CSS滤镜filter: blur()
  4. will-change属性will-change: transform
  5. 硬件加速 :使用transformopacity实现动画
  6. position: fixed(某些情况下)
  7. 硬件加速层 :使用videocanvas等元素

6.3 分层渲染的性能提升

分层渲染带来的性能提升:

  1. 独立渲染:各层可以独立绘制和合成,互不影响
  2. GPU加速:合成层可以在GPU上光栅化,减轻CPU负担
  3. 局部重绘:某层内容变化时,只需重绘该层,其他层保持不变
  4. 异步合成:主线程和合成线程分离,主线程不阻塞

6.4 合成线程是如何进行分块的?

合成线程对每个图层进行**分块(Tiling)**处理:

  1. 划分图块:将大图层划分为多个小图块(如256x256像素)
  2. 优先级排序:优先绘制视口内的图块
  3. 光栅化处理:GPU将每个图块光栅化为位图
  4. 纹理上传:将位图作为纹理上传到GPU
  5. 合成输出:将所有图块合成最终画面

6.5 绘制完成之后要怎么显示网页呢?

页面显示的完整流程:

  1. 主线程完成布局和绘制:生成绘制指令列表
  2. 提交给合成线程:主线程将绘制任务提交给合成线程
  3. 合成线程处理:合成线程进行分层、分块、光栅化
  4. GPU处理:GPU完成光栅化并将结果存入纹理
  5. 帧合成:合成线程将各层纹理合成为一帧
  6. 显示器显示:显卡将帧数据推送到显示器

6.6 为什么要进行分块呢?

分块(Tiling)的主要原因:

  1. 内存优化:不需要一次性将整个图层加载到内存
  2. 视口优先:优先处理用户可见区域,提升首屏速度
  3. 增量更新:只更新变化的图块,减少重复工作
  4. 并行处理:多个图块可以并行光栅化

七、重排和重绘

7.1 造成重排和重绘最直接的原因是什么?

造成重排和重绘最直接的原因是DOM或CSSOM的变化。当页面的布局或样式发生变化时,浏览器需要重新计算布局或重新绘制。

常见触发因素:

  • 修改元素的尺寸、位置(触发重排)
  • 修改元素的颜色、背景等视觉样式(触发重绘)
  • 添加/删除DOM元素
  • 浏览器窗口大小变化

7.2 什么是重排?

重排(Reflow/Layout)是指当渲染树中某些节点的几何属性(位置、尺寸)发生变化时,浏览器重新计算这些节点以及受影响节点的位置和尺寸的过程。

简单来说,重排就是重新计算页面布局

7.3 重排的本质是什么?

重排的本质是重新执行布局计算。浏览器需要:

  1. 从发生变化的节点开始,向上回溯到根节点
  2. 向下重新计算所有受影响节点的布局
  3. 更新渲染树中的几何信息

7.4 哪些操作会引起重排?

会引起重排的操作:

  1. 尺寸变化widthheightpaddingmargin
  2. 显示隐藏display: none(产生重排)、visibility: hidden(不产生重排)
  3. 位置变化topleftposition相关
  4. 内容变化:文本内容、图片尺寸变化
  5. 读取布局信息offsetWidthoffsetHeightclientWidthscrollTop
  6. 字体大小变化
  7. 窗口大小变化

7.5 什么是重绘?

重绘(Repaint)是指当渲染树中某些节点的视觉样式发生变化但几何属性不变时,浏览器重新绘制这些节点的过程。

简单来说,重绘就是重新绘制像素,但不重新计算布局。

7.6 思考与总结

  • 重绘和重排的异同点?

    相同点:都会消耗性能,可能导致页面卡顿,都是由于DOM或样式变化触发。

    不同点

    • 重排重新计算布局(几何属性变化),重绘只重新绘制外观(视觉样式变化)
    • 重排必然导致重绘,重绘不一定导致重排
    • 重排代价更高,重绘代价相对较低
  • 重排对性能的主要影响点是什么?

    重排对性能的主要影响是计算量大且可能连锁反应。一个元素的重排可能导致其所有祖先元素和后代元素都需要重新布局,在极端情况下可能导致整个页面的重新布局,这会阻塞主线程,导致页面卡顿。

  • 举例能够引起重排和重绘的操作有哪些?

    引起重排的操作

    • 修改widthheightmarginpadding
    • 添加/删除DOM元素
    • display: none(隐藏元素)
    • 获取offsetWidthoffsetHeight等布局信息

    引起重绘的操作

    • 修改colorbackground-color
    • 修改border-colorborder-style
    • 修改visibility(从visible改为hidden)
    • opacity变化(不涉及布局时)

总结

浏览器渲染原理是前端性能优化的核心知识。通过本文的学习,我们了解了:

  1. 渲染流程:从HTML/CSS/JS到最终显示的完整流水线
  2. DOM构建:HTML如何被解析为DOM树
  3. CSSOM构建:CSS如何被解析和应用
  4. 渲染树:DOM和CSSOM的结合产物
  5. 布局与绘制:从渲染树到屏幕像素的过程
  6. 重排与重绘:性能优化的关键点

理解这些原理,才能更好地编写高性能的前端代码,避免不必要的性能损耗。

相关推荐
Moment1 小时前
此 KFC 不是肯德基,Kafka、Flink、ClickHouse 怎么搭、何时省掉 Flink
前端·后端·面试
鹏北海-RemHusband2 小时前
JSBridge 原理详解
前端·信息与通信
T^T尚2 小时前
一个完整的项目怎么打包成为一个app
前端·uni-app
wing982 小时前
通往“全干”之路一:前端部署
前端·vue.js·全栈
阿珊和她的猫2 小时前
探究浏览器最大请求并发数:提升网页加载性能的关键
前端·javascript·vue.js
yuki_uix2 小时前
表单写到想摔键盘?聊聊前端常见的复杂状态场景
前端
whisper2 小时前
图片对比组件技
前端
简离2 小时前
解决iOS页面返回缓存问题:pageshow事件详解与实战方案
前端
前端拿破轮2 小时前
利用Github Page + Hexo 搭建专属的个人网站(二)
前端·后端·ai编程