浏览器渲染zz

一、浏览器渲染流程

HTML文档解析

  1. 为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程 ,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。

  2. 遇到 CSS暂停渲染(不暂停 HTML 解析),下载 + 解析 CSS

    1. 当主线程解析到<link>标签时,如果外部CSS文件尚未下载解析完成,主线程不会等待,而是继续解析后续HTML。
    2. 下载CSS的工作由预解析线程负责
    3. 主线程和预解析线程并行工作
    4. 但会阻塞布局 构建(必须等待CSSOM完成)
    5. CSSOM构建完成之前,JS执行会被阻塞(因为JS可能会查询元素的样式,如果CSSOM没好,查到的样式是不完整的,为了防止"读到旧样式")
    6. 遇到 JS 浏览器暂停 HTML 解析,确保 JS 执行时 DOM 树状态完整。外部 JS 并非 "阻塞下载"(预解析线程会异步下载外部 JS),而是阻塞 HTML 解析和 DOM 构建 (下载完成后执行 JS 时,主线程暂停解析);内联 JS 无下载过程,直接阻塞 HTML 解析(async/defer 可改变外部 JS 的阻塞行为)。

生成DOM树

解析的过程中遇到HTML元素会解析HTML元素最终生成DOM树

生成CSSOM树

解析的过程中遇到style标签link元素行内样式CSS样式,会解析CSS生成CSSOM树

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。

样式计算

核心步骤

  1. CSS 结构化 :将外部样式表(<link>)、内部样式表(<style>)、内联样式(style属性)转换为浏览器可理解的StyleSheet结构;

  2. 属性标准化 :将 CSS 属性值转换为标准化格式(如2em → 32pxblue → rgb(0,0,255)bold → 700);

  3. 样式 继承 与层叠

    1. 继承:子节点继承父节点的可继承属性(如font-sizecolor)(不可继承属性(如border/padding/width)需显式声明,继承属性的优先级低于显式声明,层叠时需注意)
    2. 层叠:按 "选择器权重→声明顺序→来源(用户代理样式 < 用户样式 < 内联样式)" 规则解决样式冲突;
    3. 计算最终样式 :为每个 DOM 节点生成ComputedStyle(计算样式),存储所有属性的最终值。

性能关键点

  • 复杂选择器(如div > ul li a)会增加样式计算耗时,建议简化选择器(如使用类选择器.link);

  • CSS 规则匹配是 "从右到左" 的(如.container .item先匹配.item再匹配.container),避免通配符*和深层嵌套。

    • 从右到左匹配的核心原因:减少匹配次数(先定位所有.item,再筛选其父级是否为.container,而非遍历所有.container再找子级.item

布局

核心步骤

  1. 构建布局 :基于 DOM 树和 CSSOM 树合并生成的渲染树,过滤掉不可见节点(如<head>display: none的元素),保留可见节点

  2. 布局计算

    1. 基于布局树,从根节点开始,算每个元素的几何属性------包括元素的位置(left、top、right、bottom)、大小(width、height、padding、margin)、以及元素之间的关系。
    2. 遵循盒模型规则,结合视口大小、父节点布局约束完成计算;
  3. 布局树仅包含可见节点的几何布局信息,与 DOM 树结构不完全一致(如display: none节点被剔除)。布局是"自上而下"的:从根节点开始,依次计算每个子节点的几何属性,因为父元素的大小和位置会影响子元素。

  4. 布局是"流式布局":浏览器会按照文档流的顺序,依次计算元素的位置,一旦计算完成,就会确定元素在页面中的最终位置(除非后续触发重排)。

关键特性

  • 重排(Reflow) :布局计算是递归的,子节点布局变化会触发父节点重新计算,开销极大;
  • 布局抖动(Layout Thrashing) :频繁读取 + 修改布局属性(如offsetTop+style.top)会强制浏览器反复计算布局,导致性能暴跌(使用requestAnimationFrame批量操作,或先读取所有布局属性缓存,再批量修改,避免'读 - 改 - 读 - 改'的循环触发多次重排)

当修改了节点的几何属性,如大小、位置,就需要重新计算布局,这个过程也叫做重排

获取节点的几何属性时,如 offsetWidth / getBoundingClientRect/clientWidth强制重排

分层

  • 主线程会使用一套复杂的策略对整个布局树中进行分层。
  • 将页面进行分层,之后某个层变化时,就可以单独更新 这一个图层,从而避免了全页面的更新,提高效率。
  • 分层不仅取决于transformopacity ,还取决于videocanvasiframewill-change以及复杂的堆叠上下文(z-index)
  • 避免 过度创建合成层 的问题,每个图层需占用 GPU 显存,图层过多(如数百个独立图层)会导致显存不足,反而触发 GPU 卡顿,因此will-change需谨慎使用(仅给高频动画元素声明)。

绘制

绘制阶段为每个图层生成绘制列表,定义 "先画什么、后画什么"(如先画背景,再画边框,最后画文本),

核心流程

  1. 渲染引擎将图层的绘制过程拆解为原子化指令(如 "绘制矩形""绘制文本""绘制渐变");
  2. 按绘制顺序组合指令生成绘制列表(Paint List);

关键特性

  1. 核心绘制顺序:背景色 → 背景图 → 边框 → 文本 / 替换元素 → 子元素;
  2. 子元素绘制规则:z-index 优先(数值大的后画),无 z-index 按 DOM 顺序(后写的后画)
  3. 关键原则:后绘制的内容会覆盖先绘制的,子元素默认在父元素文本之上绘制。
  4. 绘制是 分层 :不同层可以独立绘制

性能关键点

  • 绘制指令越复杂(如多层阴影、渐变),绘制耗时越长;
  • 避免给大尺寸图层添加复杂绘制属性(如box-shadow)。

光栅化

上面我们已经获得了文档结构、元素的样式、元素的几何关系、绘画顺序,接下来把这些信息转化为显示器中的像素才能显示,这个转化的过程,就叫做光栅化。

核心流程

  1. 接收绘制列表:合成线程从主线程接收每个图层的绘制列表
  2. 分块: 将大图层切割成小块(通常是256x256或512x512像素的图块)
  3. 优先级排序 优先光栅化视口内的图块(用户当前可见区域)
  4. 执行光栅化: 将每个图块的绘制指令转换为 位图 (实际像素)
  5. 存储 位图 将生成的位图存储在GPU 内存(或CPU内存)中,供合成使用

合成

合成是将光栅化后生成的一块块位图,按照正确的层叠顺序合并成最终画面,显示在屏幕上的过程。

核心步骤

  1. 接收 位图 : 合成线程从 GPU 显存中获取已完成光栅化的图块位图
  2. 计算变换: 根据图层的transform、opacity等属性,计算每个图块需要应用的变换矩阵
  3. 绘制图块到合成层: 将每个图块的位图绘制到对应的合成层(按正确位置和变换)
  4. 合并合成层: 按照层叠顺序(z-index、堆叠上下文)将所有合成层合并成一张最终图像
  5. 提交显示: 将最终图像提交给GPU的显示缓冲区,等待屏幕刷新显示

性能关键点

  1. 优先使用「仅触发合成」的属性,使用 transform (位移/缩放/旋转) 和 opacity。它们只影响合成,GPU 处理极快。
  2. 避免过度分层,如果图层太多、太大(显存爆炸),或者图层间依赖关系太复杂(导致无法并行合成),GPU 也会忙不过来,导致掉帧。

二、CPU和GPU

  • CPU(主线程):主导逻辑运算、DOM 操作与布局计算。任务繁重且串行执行,高负载下易引发主线程阻塞,导致页面卡顿。
  • GPU(合成线程):专攻图形渲染、位移、旋转与缩放。利用硬件加速并行处理,高效丝滑,且不占用主线程资源

CPU vs GPU 本质区别

对比维度 CPU GPU
核心数量 4-16 个高性能核心 数千个简单核心
并行能力 同时处理几个任务 同时处理几千个任务
设计目标 复杂逻辑控制 大规模并行计算
适合任务 串行、分支预测 矩阵、图像、并行

渲染流程中的分工

CPU 负责"怎么画"的复杂决策,GPU 负责"快点画"的并行执行,两者配合实现流畅的渲染。

渲染阶段 执行者 核心原因
HTML 解析 CPU 复杂文本解析、树结构(DOM)构建
CSS 解析 CPU 样式规则解析、选择器匹配、构建 CSSOM
样式计算 CPU 样式继承、层叠规则、属性值标准化计算
布局 (Layout) CPU 复杂几何计算(宽 / 高 / 坐标)、元素依赖关系处理
分层 CPU 分层策略判断、堆叠上下文分析
绘制 (Paint) CPU 生成原子化绘制指令(不直接生成像素)
光栅化 GPU 优先(或 CPU) 并行像素填充,将指令转换为位图(GPU 并行效率远高于 CPU)
合成 GPU 并行图层合并、变换矩阵运算(transform/opacity 仅触发此阶段)

前期阶段( HTML 解析 → 绘制): CPU 主场 这些阶段涉及复杂的逻辑判断、递归计算和指令生成,属于串行、高逻辑密度的任务,CPU 是唯一高效执行者;若主线程被阻塞(如长时间 JS 执行),会直接导致渲染卡顿。

后期阶段(光栅化 → 合成): GPU 主场 这两个阶段是并行 、像素级运算 ,GPU 天生擅长处理大规模并行计算,因此性能远优于 CPU;修改 transform/opacity 时,浏览器可跳过前期阶段,仅触发 GPU 合成,是前端性能优化的方案。


三、重排、重绘与合成

页面交互过程中,JS/CSS 修改会触发渲染流水线的局部更新,按开销从高到低分为三类:

类型 触发条件 涉及渲染阶段 性能开销 优化优先级
重排(Reflow) 几何属性变化:宽高、位置、display、DOM 增删 布局→分层→绘制→光栅化→合成 极高 最高
重绘(Repaint) 绘制属性变化:颜色、背景、阴影、边框色 绘制→光栅化→合成 中等
合成(Composite) 合成属性变化:transform、opacity 仅合成阶段 极低 低(优先用)

重排触发场景

  • 修改几何属性:width: 200pxleft: 10pxmargin: 8px
  • 增删 / 移动 DOM 节点:appendChildremoveChildinsertBefore
  • 窗口操作:resize、普通scroll 仅触发合成(无重排),只有 scroll 时触发了元素几何位置变化(如固定定位元素跟随滚动)才会重排;
  • 读取布局属性:offsetTopclientWidthgetComputedStyle(强制浏览器提前完成重排)。

重绘触发场景

  • 修改颜色属性:color: redbackground-color: #000
  • 修改边框 / 阴影:border-color: bluebox-shadow: 0 0 10px #000
  • 修改文本样式:text-shadow: 1px 1px 2px #333
  • 修改背景:background-image: url(new.png)background-position: center

合成触发场景

  • transformtranslate/scale/rotate/skew
  • opacityopacity: 0.5
  • will-change:提前声明元素即将变化的属性。

四、渲染性能优化

渲染阶段 优化目标 核心优化手段
网络请求 减少阻塞 预解析、关键资源优化
HTML解析 减少阻塞 精简HTML、JS异步
CSS解析 减少阻塞 关键CSS内联、避免@import
样式计算 减少耗时 简化选择器、避免通配符
布局 避免重排 批量操作、离线DOM
分层 合理分层 will-change、独立图层
绘制 减少重绘 仅合成属性、避免大面积重绘
光栅化 加速光栅化 视口优先、GPU加速
合成 提升合成效率 transform/opacity、图层管理

减少重排/重绘

  • 批量修改 DOM 和样式(如使用 DocumentFragment 批量添加节点,或先隐藏节点 display: none,修改完成后再显示)。
  • 避免频繁读取布局属性(如 offsetWidth、clientHeight、getBoundingClientRect()),若需多次读取,可缓存结果。
  • 使用 transform 和 opacity 实现动画(仅触发合成,不触发布局和重绘),替代修改 width、height、top 等几何属性。
  • 避免 表格布局:表格布局的重排成本极高(一个单元格变化会导致整个表格重新布局),优先用 Flex/Grid 布局。

利用GPU加速

  1. 动画用 transform opacity

    1. transform(如translatescale)和opacity的修改仅触发合成,不触发重排和重绘,是性能最优的动画实现方式:
  2. 创建独立 图层

    1. 对频繁动画的元素,用will-change提示浏览器创建独立图层,提前做好优化准备

避免渲染阻塞

  1. JS 优化

    1. 首屏非必需的 JS 用async/defer加载;
    2. 大型 JS 文件用代码分割(Code Splitting),按需加载;
  2. CSS 优化

    1. 外部 CSS 文件放在<head>中(确保样式优先加载);
    2. 非首屏 CSS 用媒体查询media="print"等,不阻塞首屏渲染:

script标签中defer和async的区别

如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。

其区别如下:

  • 无:遇到script标签时,浏览器暂停 HTML 解析,先同步下载 JS 文件;下载完成后立即执行 JS 代码,执行完毕后才恢复 HTML 解析,JS 下载 / 执行全程阻塞 HTML 解析,若 JS 文件体积大、下载慢,会导致页面长时间白屏,首屏渲染延迟;
  • defer:遇到script标签时,浏览器开始异步下载,HTML页面解析完才执行JS文件。立即下载,但延迟执行(整个页面都解析完毕之后再执行,不阻塞)。多个带async属性的标签,不能保证加载的顺序;
  • async:遇到script标签时,浏览器开始异步下载,下载完成后如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析(可能会阻塞)。多个带defer属性的标签,按照加载顺序执行;
相关推荐
Jackson__19 小时前
Agent Skill 是什么?
前端·agent·ai编程
韭菜炒大葱19 小时前
前端经典面试题:从 URL 输入到页面展示,中间经历了什么?
前端·http·面试
swipe20 小时前
纯函数、柯里化与函数组合:从原理到源码,构建更可维护的前端代码体系
前端·javascript·面试
远山枫谷20 小时前
uniapp + Vue 自定义组件封装:自定义样式从入门到实战
前端·vue.js
Lee川20 小时前
JavaScript 中的 `this` 与变量查找:一场关于“身份”与“作用域”的深度博弈
前端·javascript·面试
顺遂1 天前
基于Rokid CXR-M SDK的引导式作业辅导系统设计与实现
前端
代码搬运媛1 天前
Generator 迭代器协议 & co 库底层原理+实战
前端
前端拿破轮1 天前
从0到1搭建个人网站(三):用 Cloudflare R2 + PicGo 搭建高速图床
前端·后端·面试
功能啥都不会1 天前
PM2 使用指南 - 踩坑记录
前端