浏览器渲染过程
整个渲染过程分为多个阶段
每个阶段都有明确的输入输出 上一个阶段的输出会成为下一个阶段的输入
- 渲染流水线

- 解析html得到Dom树和cssom树

解析过程中如果遇到CSS解析CSS 遇到JS执行JS 为了提高执行效率 浏览器在开始解析前 会启动要给预解析的线程 率先下载HTML中的外部CSS文件和外部的JS文件。
如果主线程解析到Link位置 此时外部的CSS文件还没有下载解析好 主线程不会等待 继续解析后续的HTML 这是因为下载和解析CSS的工作是在预解析线程中进行的 这就是CSS不会阻塞HTML的根本原因
如果主线程解析到script位置 会停止解析HTML 转而等待JS文件下载好,并将全局代码解析执行完成后 才能继续解析HTML 这是因为JS代码的执行过程可能会修改当前的dom树 所以dom树的生成必须暂停 这就是js会阻塞HTML解析的根本原因
在第一步完成后 会得到DOM树和CSSOM树,浏览器的默认样式 、内部样式、外部样式、行内样式、均会包含存在CSSOM树中。
- 样式计算-得到dom和cssom后进行样式计算

主线程会遍历得到的dom树 依次为树中的每个节点计算出他的最终的样式
在这一过程中 很多预设值会变为绝对值 比如red会变成rgb(255,0,0)相对单位会变成绝对单位 比如em会变为px
这一步完成后 会得到一颗带有样式的DOM树
- 布局-布局完成后会得到布局树

布局阶段会一次遍历DOM树的每一个节点 计算每个节点的几何信息 例如节点的宽高、相对包含块的位置
大部分时候 DOM树和布局树并非一一对应
比如display:none 的节点没有几何信息 因此不会生成到布局树 又比如使用了伪元素选择器 虽然DOM树中不存在这些伪元素节点 但他们拥有几何信息 所以会生成到布局树中 还有匿名行盒 匿名块盒等等都会导致DOM树和布局树无法一一对应。
- 分层-将页面分为很多层级 避免页面变化全部重绘

主线程会使用一套复杂的策略对整个布局树中进行分层
分层的好处在于 将来某一个层改变后 仅会对该层进行后续处理 从而提升效率
滚动条 堆叠上下文 transform opacity 等样式都会或多或少的影响分层结果 也可以通过will-change属性更大程度的影响分层结果
- will-change语法与含义

- will-change的副作用

- 绘制-生成绘制的指令 并非直接画像素点

这里的绘制 是每层生成的如何绘制的指令
主线程会为每个层单独产生绘制指令集 用于描述这一层的内容该如何画出来

-
分块 将每一层分为多个小的区域

分块的工作是交给多个线程同时进行的

完成绘制后 主线程将每个图层的绘制信息提交给合成线程 剩余工作将有合成线程完成
合成线程首先对每个图层进行分块 将其划分为更多的小区域
他会从线程池中拿取多个线程来完成分块工作
- 光栅化-将每个块变为位图 优先处理靠近视口的块

此过程会用到gpu加速
分块完成后 会进入光栅化
合成线程会将块信息交给GPU进程 以极高的速度完成光栅化
GPU进程会开启多个线程来完成光栅化 并且优先处理靠近视口区域的块
光栅化的结果 就是一块一块的位图
- 画-合成线程计算出每个位图在屏幕的位置 交给gpu进行最终呈现

合成线程拿到每个层、每个块的位图后 生成一个个指引(quad)信息
指引会标识出每个位图应该画到屏幕的哪个位置 以及会考虑到旋转、缩放等变形
变形发生在合成线程 与渲染主线程无关 这就是transform效率高的本质原因
合成线程会把quad提交给GPU进程 由GPU进程产生系统调用 提交给GPU硬件 完成最终的屏幕成像
完整过程:

URL到页面的过程
为什么transform的效率高
因为transform既不会影响布局也不会影响绘制指令 他影响的只是渲染流程的最后一个draw阶段
由于draw阶段在合成线程中 所以transform的变化几乎不会影响渲染主线程 反之 渲染主线程无论如何忙碌 也不会影响transform
1. GPU加速
- 原理:
transform操作(如translate、scale、rotate等)通常会被浏览器识别为可以利用GPU(图形处理单元)加速的动画。GPU专为图形计算设计,处理这些变换操作比CPU更高效。 - 对比:其他DOM操作(如直接修改元素的
left、top、width、height等属性)通常由CPU处理。CPU虽然功能强大,但在处理大量图形变换时效率不如GPU。例如,频繁地修改left和top来移动元素,会触发浏览器的重排(reflow)和重绘(repaint),这非常消耗CPU资源。
2. 减少重排和重绘
- 原理:
transform操作不会影响其他元素的布局。它只改变元素的视觉效果,而不会改变文档流。这意味着浏览器不需要重新计算其他元素的布局(重排)或重新绘制整个页面(重绘)。 - 对比:修改元素的
width、height、margin等属性会改变文档流,导致浏览器进行重排和重绘。例如,改变一个元素的宽度可能会影响其相邻元素的位置,浏览器需要重新计算所有相关元素的布局,这个过程非常耗时。
3. 硬件加速的合成层
- 原理:当使用
transform时,浏览器会将相关元素提升到一个单独的合成层(compositing layer)。这些层可以独立于主页面进行渲染和更新,进一步减少了对主页面的干扰。 - 对比:普通DOM操作通常在主页面的渲染上下文中进行,任何对主页面的修改都可能触发全局的重排和重绘,效率较低。
4. 独立于文档流
- 原理:
transform操作是基于CSS3的3D渲染上下文,元素在变换过程中独立于文档流。这意味着即使元素在视觉上发生了移动或变形,它在文档流中的位置仍然保持不变。 - 对比:其他DOM操作(如改变
position属性)会直接改变元素在文档流中的位置,这可能会引发复杂的布局计算。
5. 浏览器优化
- 原理:现代浏览器对
transform进行了大量优化,以支持高性能的动画和交互效果。浏览器内部对transform操作有专门的处理机制,确保其高效运行。 - 对比:其他DOM操作可能没有得到类似的优化,特别是在涉及频繁的布局和样式更新时。
实际应用建议
- 动画:如果需要实现平滑的动画效果(如移动、缩放、旋转等),优先使用
transform。 - 性能优化:在需要频繁更新元素位置或状态时,尽量避免直接操作DOM的布局属性,而是使用
transform来实现视觉效果。 - 组合使用:可以将
transform与其他CSS属性(如opacity)结合使用,进一步提升性能,因为这些属性也支持GPU加速。
浏览器的缓存
浏览器缓存是浏览器在本地磁盘对用户请求的资源进行存储的过程。合理使用缓存可以减少网络请求,提高页面加载速度,减轻服务器压力
浏览器缓存主要分为两类:
强制缓存(Force Cache)
- 不需要发送请求到服务器,直接从本地获取资源
- 通过 HTTP 响应头中的 Expires 和 Cache-Control 控制
协商缓存(Negotiation Cache)
- 需要向服务器发送请求进行协商,由服务器决定是否使用缓存
- 通过 Last-Modified/If-Modified-Since 和 ETag/If-None-Match 控制
强制缓存:
Expires:response header 里的过期时间 浏览器再次加载资源时 如果在这个过期时间内 则命中强缓存
Cache-Control:当值为max-age = 300 时 则代表在这个请求正确返回时间的五分钟内再次加载资源 就会命中强缓存
cache-control除了该字段外 还有下面几个比较常用的设置值:
-no-cache:不再使用本地缓存 需要使用协商缓存 先与服务器确认返回的响应是否被更改 如果之前的响应中存在Etag 那么请求的时候会与服务端验证 如果资源未被更改 则可以避免重新下载
-no-store:直接进至浏览器缓存数据 每次用户请求该资源 都会下载完整的资源
-public:可以被所有的用户缓存 包括终端用户和CDN等中间代理服务器
-private:只能被终端用户的浏览器缓存 不允许CDN等中继缓存服务器对其缓存
- Expires:设置以分钟为单位的绝对过期时间 设置相对过期时间 max-age指明以秒为单位的缓存时间
- Expires优先级比Cache-Control低 同时设置Expires和Cache-Control则后者生效
协商缓存: 设置协商缓存 : cache-control:no-cache(不强制缓存)
Last-Modify/If-Modify-Since:浏览器第一次请求一个资源的时候 服务器返回的header中会加上Last-Modify, Last-modify是一个时间标识 该资源的最后修改时间;当浏览器再次请求该资源时 request的请求头中会包含if-Modify-since 该值为缓存之前返回的LastModify 服务器收到If-modify-since后 根据资源的最后修改时间判断是否命中缓存
Last-Modify-Since的值是资源最后修改时间
Etag/if-None-Match:web服务器响应请求时 告诉浏览器当前资源在服务器上的唯一标识 (生成规则由服务器决定) If-None-Match:发现资源带有Etag声明 则再次向web服务器请求时带上头If-None-Match(Etag的值) Web服务器收到请求后发现有头If-None-Match则与被请求的资源的相应校验串进行比对 决定是否命中协商缓存
Etag的值是文件对应的hash值 但是会加大服务器开销 这个hash是需要生成的
Etag和Last-Modify的作用和用法 他们的区别:
- Etag要优于last-modify lastmodify的时间单位是秒 如果一个文件在一秒内改变了多次 那么他们的last-modify其实并没有体现出来服务器的资源修改了 但是Etag每次都会改变确保了精度
- 在性能上Etag逊色于last-modify last-modify只需要记录时间 而Etag需要服务器通过算法来计算出一个hash值