认识你自己很难,但认识你自己的时间,却是每个人都能做到的 — 彼得·德鲁克
大家好,我是柒八九。
前言
最近,公司在做技术改造升级,老旧项目优化。因为是B端项目居多,所以优化方向也是基于浏览器端的项目改造和处理。
然后,在进行实操时发现,资源的加载是很影响页面性能的。而针对资源加载而言,根据资源的重要性,又可以做一些符合业务逻辑的处理。今天,我们就来讲讲关于影响页面资源加载的属性 – fetchpriority
。
Last but not least
,这偏文章算是一个引子,之后我们会出一篇关于如何对一个现有网站的性能分析。同时,因为我们有文章分类,并且该文章内容大部分都是关于浏览器的。所以,我们就将其归类为浏览器相关的文章体系。
还有,之前我们写过浏览器相关
的知识点,如果想了解该系列文章(浏览器相关),可以参考我们已经发布的文章。如下是往期文章。
你能所学到的知识点
- 前置知识点
- Fetch Priority
- fetchpriority
- 案例分析
- 将 preload 和 fetchpriority 合并
- fetchpriority 的好处
好了,天不早了,干点正事哇。
前置知识点
页面阶段
通常一个页面有三个阶段
- 加载阶段
- 是指从发出请求到渲染出完整页面的过程
- 影响到这个阶段的主要因素有网络和 JavaScript 脚本
- 交互阶段
- 主要是从页面加载完成到用户交互的整个过程
- 影响到这个阶段的主要因素是 JavaScript 脚本
- 关闭阶段
- 主要是用户发出关闭指令后页面所做的一些清理操作
加载阶段-资源获取
我们可以通过PerformanceNavigationTiming来简单的看一下,浏览器在资源加载时候,所经历的大体流程。
在上图中,Resource Timing
就是在为了做资源获取的所做的准备工作。(这里涉及到网络处理相关的,我们之前也有文章介绍,如果感兴趣,可以移步到指定文章,按个人情况,自行获取)
我们将上图,做一个精简处理。
然后,我们简单的把上述的各个阶段,做一个简单的介绍。
时间戳 | 描述 |
---|---|
startTime | 资源加载过程开始之前的时间戳 |
redirectStart | 触发重定向时的时间戳 |
redirectEnd | 接收到最后一个重定向响应的最后一个字节之后的时间戳 |
workerStart | Service Worker 线程开始之前的时间戳 |
fetchStart |
浏览器开始获取资源之前的时间戳 |
domainLookupStart |
浏览器开始进行资源的域名查询之前的时间戳 |
domainLookupEnd | 浏览器完成资源的域名查询之后的时间戳 |
connectStart |
浏览器开始建立与服务器的连接以检索资源之前的时间戳 |
secureConnectionStart | 如果资源通过安全连接加载,则是在浏览器开始握手过程以保证当前连接安全之前的时间戳 |
connectEnd | 浏览器完成与服务器建立连接之后的时间戳 |
requestStart |
浏览器开始从服务器、缓存或本地资源请求资源之前的时间戳 |
responseStart | 浏览器从服务器、缓存或本地资源接收到响应的第一个字节之后的时间戳 |
responseEnd | 浏览器接收到资源的最后一个字节之后的时间戳,或者在传输连接关闭之前的时间戳(以先到者为准) |
这些时间戳描述了资源加载过程中的不同阶段,通过它们可以了解各个阶段的时间信息,从而进行性能优化和分析。
加载阶段-页面渲染
这里可以直接参考我们之前的文章像素是怎样练成的。 这里就不在赘述了。(看过的人,都说好;如果不好,记得回来打我?)
Chromium 加载资源的阶段
Chromium
浏览器在加载资源时采用了两个阶段。
- {紧凑模式|Tight mode}
- {空闲模式|Idle mode}
{紧凑模式|Tight mode}是初始阶段,它会限制加载低优先级的资源,直到文档的<body>
被追加到文档中(基本上,在<head>
中的所有阻塞脚本
执行完毕后)。在紧凑模式
下,只有在发现这些低优先级资源时,同时存在不超过2个正在进行的请求,才会加载它们。
除了 {紧凑模式|Tight mode}之外,Chromium
浏览器还有一个阶段称为 {空闲模式|Idle mode}。
在 {空闲模式|Idle mode} 中,浏览器会在页面空闲时加载资源。它会根据资源的优先级
和是否可见
来决定何时加载资源,以提高性能和用户体验。
资源类型
{关键资源| Critical Resource}是指对网页性能和加载速度影响最大的资源。它们是在网页的{关键渲染路径|Critical Rendering Path}上,对
首次渲染
和用户交互
有着直接影响的资源。
或者说:关键资源
就是所有可能阻碍页面渲染的资源
关键渲染路径
浏览器的{关键渲染路径|Critical Rendering Path}是指浏览器在
加载
和渲染网页
时所经过的一系列关键步骤,以便将网页内容呈现给用户。
下面是关键渲染路径的主要步骤以及对应的说明:
步骤 | 说明 |
---|---|
解析 HTML | 解析服务器返回的 HTML 文档,构建 DOM 树。 |
解析 CSS | 解析 CSS 样式表,构建 CSSOM 树。 |
合成渲染树 | 结合 DOM 树和 CSSOM 树生成渲染树,包括可见元素 和样式布局信息。 |
布局计算 | 对渲染树进行布局计算,确定元素在屏幕上的位置 和大小 。 |
绘制 | 根据渲染树和布局计算的结果,将页面内容绘制到屏幕上。 |
栅格化 | 将绘制的内容拆分成小的图块 ,方便传输和显示。 |
合成 | 将栅格化后的图块组合成一帧画面 ,显示在屏幕上。 |
这些步骤构成了浏览器渲染页面的关键路径。关于详细的浏览器如何渲染页面的步骤,可以参考像素是怎样练成的。
渲染阻断资源
{渲染阻断资源| Render-Blocking Resources}是指在网页加载过程中会阻止浏览器进行渲染的资源。
这些资源需要在浏览器能够继续渲染页面之前先加载和处理。渲染阻断资源
的加载时间较长,会延迟网页的首次渲染
和用户能够与页面进行交互
的时间。
以下是常见的渲染阻断资源
:
-
CSS
:外部样式表(CSS)文件是常见的渲染阻断资源
。- 浏览器在解析 HTML 时会发现外部 CSS 文件,并且需要等待 CSS 文件
下载
和解析完成
后才能继续渲染页面。 - 如果 CSS 文件体积较大或加载时间较长,将会显著延迟页面的渲染。
- 浏览器在解析 HTML 时会发现外部 CSS 文件,并且需要等待 CSS 文件
-
JavaScript
:JavaScript
脚本也可以成为渲染阻断资源
。- 当浏览器遇到
<script>
标签时,会阻塞渲染,等待JavaScript
文件的下载
和执行完成
后才能继续渲染页面。 - 如果
JavaScript
文件较大或执行时间较长,会对页面的渲染速度产生较大影响。
- 当浏览器遇到
-
字体:自定义字体文件(如
WOFF
、WOFF2
、TTF
等)也可能成为渲染阻断资源- 当网页使用自定义字体时,浏览器需要
下载
和解析字体文件
后才能正确渲染文本内容 - 如果字体文件较大,会延迟页面的渲染。
- 当网页使用自定义字体时,浏览器需要
换句话说,渲染阻塞资源
是一个组件,它将不允许浏览器渲染整个DOM树,直到给定的资源被完全加载和解析/执行。在渲染阻塞资源
完全加载之前,你无法渲染树。
解析器阻断资源
{解析器阻断资源|Parser-Blocking Resources}是指在浏览器解析 HTML 文档时会阻塞解析器的资源。
这些资源需要在浏览器能够继续解析文档之前先加载和处理。解析器阻断资源的加载时间较长,会延迟整个文档的解析和渲染。
以下是常见的解析器阻断资源
:
-
外部脚本
:外部 JavaScript 脚本是常见的解析器阻断资源
。- 当浏览器遇到
<script>
标签引用外部 JavaScript 文件时,解析器会暂停解析 HTML 文档,等待 JavaScript 文件的下载
和执行完成
后才能继续解析文档。
- 当浏览器遇到
-
外部样式表
:外部 CSS 样式表也可以成为解析器阻断资源。- 当浏览器遇到
<link rel="stylesheet">
标签引用外部 CSS 文件时,解析器会停止解析文档,等待 CSS 文件的下载
和解析完成
后才能继续解析。
- 当浏览器遇到
-
图像资源
:在某些情况下,大型图像资源也可能成为解析器阻断资源。- 当浏览器遇到
<img>
标签或 CSS 中的background-image
属性引用图像时,解析器会暂停解析文档,等待图像资源的下载完成后才能继续解析。
- 当浏览器遇到
换句话说,当需要下载和执行解析器阻断资源
时,浏览器会暂停执行和构建DOM树。当解析器阻断资源
被执行完后,DOM树的构建才继续进行。
渲染阻断资源 VS 解析器阻断资源
渲染阻断资源
和解析器阻断资源
是两种不同类型的资源,它们在浏览器加载和处理过程中起着不同的作用。
-
渲染阻断资源
:渲染阻断资源是指在网页加载过程中会阻止浏览器进行渲染的资源。- 这些资源需要在浏览器能够继续渲染页面之前先加载和处理。
- 常见的渲染阻断资源包括
外部样式表(CSS)
和JavaScript 脚本
。 - 渲染阻断资源会延迟网页的
首次渲染
(First Paint)和用户能够与页面进行交互的时间(TTI
)。
-
解析器阻断资源
:解析器阻断资源是指在浏览器解析 HTML 文档时会阻塞解析器的资源。- 这些资源需要在浏览器能够继续解析文档之前先加载和处理。
- 常见的解析器阻断资源包括
外部 JavaScript 脚本
和外部样式表
。 - 解析器阻断资源会延迟整个文档的解析过程和后续资源的请求。
区别:
下面是渲染阻断资源和解析器阻断资源的区别
特性 | 渲染阻断资源 | 解析器阻断资源 |
---|---|---|
作用对象 | 页面的渲染过程 | 页面的解析过程 |
阻塞时机 | 在浏览器进行页面渲染之前阻塞 | 在浏览器进行 HTML 解析之前阻塞 |
影响范围 | 页面的渲染速度和用户交互能力 | 整个文档的解析速度和后续资源的加载 |
常见类型 | 外部样式表和 JavaScript 脚本 | 外部 JavaScript 脚本和外部样式表 |
某些资源可能同时具有
渲染阻断
和解析器阻断
的特性,具体影响取决于资源的类型和加载顺序。
当然,在我们平时开发和项目优化中,我们不需要对资源做是否是
渲染阻断资源
和解析器阻断资源
的区分。 我们也可以一股脑的认为上述所提出的资源都是阻塞资源。最终的结果就是影响页面的首次渲染和页面交互时间。
查看chromium
如果大家对chrome
或者chromium
中源码结构或者一些内部实现感兴趣。可以通过Chromium Code Search进行查看。
因为,里面的Chromium
的内部实现,有很多并且专业术语也冗余庞杂,如果想了解这块的东西,我们以后可以单出一篇文章,给大家简单介绍一下,如何查看Chromium
源码。
WebPageTest
在进行网页性能分析其实有很多工具和插件。例如,Chrome
自带的Ligthhouse
/Performance
/Recorder
/Performance insights
等。(针对这块也是有很大的学问和实践规范。后期,我们也会有相关的文章和系列)。
而今天的文章的一些图文信息,我们使用WebPageTest
。
WebPageTest
是一个免费的在线性能测试工具,用于评估网页加载速度和性能。它可以帮助开发人员和网站管理员分析网页的性能,并提供改进性能的建议。
以下是 WebPageTest
的一些主要特点和功能:
-
多地点测试:
WebPageTest
提供了全球各地的测试服务器,可以选择多个地点进行性能测试,以模拟不同地区用户的加载体验。 -
多种浏览器和设备:
WebPageTest
支持使用多种浏览器和设备进行测试,包括桌面浏览器(如Chrome
、Firefox
、Safari
)和移动设备浏览器(如iOS和Android)。 -
多种测试选项:
WebPageTest
提供了丰富的测试选项,可以对页面加载过程进行详细的性能分析,包括测量页面加载时间、网络请求和响应时间、渲染时间等。 -
完整的性能报告:测试完成后,
WebPageTest
会生成详细的性能报告,包括加载时间的时间线图、资源加载顺序、性能指标(如首次字节时间、首次可交互时间等)、页面截图等。 -
延迟和带宽模拟:
WebPageTest
允许模拟不同的网络条件,包括延迟和带宽限制,以测试在不同网络环境下的页面加载速度和性能。 -
性能优化建议:
WebPageTest
提供了针对页面性能的建议和优化提示,帮助开发人员识别和解决性能瓶颈,改进页面加载速度和用户体验。
WebPageTest
是一个功能强大的性能测试工具,广泛应用于网站开发和优化过程中。通过使用 WebPageTest
,开发人员可以更好地了解页面加载过程中的性能瓶颈,并采取相应的优化措施,提升网站的用户体验和性能。
Fetch Priority
Fetch Priority API
用于向浏览器指示资源的相对优先级。可以通过向 <img>
、<link>
、<script>
和 <iframe>
元素添加 fetchpriority
属性或通过 Fetch API
上的 priority
属性来配置优先级。
浏览器主要根据请求的类型和在文档标记中的位置来确定请求的优先级。
下面是一些示例和相应的代码,以说明不同资源的优先级:
- CSS 文件:在文档的
<head>
中请求的 CSS 文件通常被赋予最高优先级。
<head>
<link rel="stylesheet" href="styles.css">
</head>
- JavaScript 文件:一般情况下,没有
async
或defer
属性的<script>
元素将被视为阻塞渲染资源
,并被赋予较高优先级。
<script src="script.js"></script>
- 图像:图像通常具有较低的默认优先级。我们可以通过添加
fetchpriority
属性来调整图像的优先级。
<img src="image.jpg" fetchpriority="high">
- 预加载资源:使用
<link rel="preload">
元素可以预先加载资源,但它不会直接影响资源的优先级。
<link rel="preload" href="resource.js" as="script">
除了一些特殊的资源,对于其他常规的资源,浏览器按照发现资源的顺序下载具有相同优先级
的资源。
fetchpriority
fetchpriority
属性可以用于提示浏览器增加
或降低
所请求资源的优先级。
该枚举属性可以有三个值:
high
: 该资源相对于其默认优先级更高low
: 该资源相对于其默认优先级更低auto
: 默认值
从Chromium
源码中,我们找到对应的定义。
举例说明
<img src="/789.jpg" alt="前端柒八九" fetchpriority="high" />
在上面的示例中,我们提示浏览器 <img>
的优先级比其默认优先级高。
对于 fetch
方法上的 priority
属性,同样支持相同的值。
fetch("/api/data.json", { priority: 'high' })
在上面的 fetch
请求中,我们向浏览器指示该 fetch
请求的优先级较其默认优先级更高。
Default priority
Fetch Priority API
可以增加
或降低
资源相对于其默认优先级的优先级。
例如,默认情况下,图片始终具有低优先级。将 fetchpriority="high"
分配给图片将把它们的优先级提升为高优先级。
另一方面,渲染阻塞的样式表
默认情况下具有最高优先级。将其分配为 fetchpriority="low"
将把其优先级降低为高优先级,而不是低优先级。
fetchpriority
用于相对于默认值调整资源的优先级,而不是显式设置其值。
在 Chromium
的资源优先级文档中记录了 Fetch Priority
对资源优先级的影响,包括不同的资源类型,它们的默认优先级(◉),以及使用 fetchpriority="high"
(⬆)和 fetchpriority="low"
(⬇)时的结果优先级。
-
使用
"as"
进行preload
或使用"type"
进行fetch
的操作将使用它们请求的类型的优先级,除非另有说明(比如字体)。- 例如,使用
"preload as=stylesheet"
将使用最高优先级。如果没有指定"as"
,它们将表现得像XHR(XMLHttpRequest
)。
- 例如,使用
-
"Early"
指的是在请求任何非预加载的图像之前进行的请求(”late”指的是之后)。 -
当CSS的
媒体类型
不匹配时,预加载扫描器不会获取该CSS,而只有当主解析器到达时才会处理它,这通常意味着它将在非常晚的时候被获取,并且具有"late"
优先级。
优先级变化
图像始终以低优先级开始。如果在布局过程中发现图像在视口内,则优先级将提升为高优先级
,尽管这可能发生在加载过程中的相当晚的阶段。
位于页面底部并阻塞的脚本为中等优先级
。但是,如果主HTML解析器到达并被阻塞,优先级将提升为高优先级。(前面我们讲过,js
是解析器阻断资源
)
网络堆栈优先级名称
顺便说一嘴,在Chrome
中Network
的DevTool
中也会显示资源优先级。
Chrome
网络堆栈中显示的资源优先级名称与Chromium
中的Blink
中有些不同。但是,它们在自己的规则范围中,是能正确表达各个资源之间的优先级关系的。
完整的映射如下:
Chrome网络堆栈优先级名称 | Chrome优先级名称 |
---|---|
IDLE | Lowest |
LOWEST | Low |
LOW | Medium |
MEDIUM | High |
HIGHEST | Highest |
紧凑模式(Tight mode)
在前面的前置知识中我们已经讲过何为{紧凑模式|Tight mode},并且还将其与{空闲模式|Idle mode}进行了对比。这里就不在赘述了。
在上面的瀑布图中,您可以看到资源 image-1.jpg
直到 style-2.css
完成下载后才开始下载,即使它已经被解析器探知。此时,只剩下一个正在处理的资源 – script.js
,所以浏览器开始下载低优先级的图片。
一旦所有位于 <head>
中的阻塞脚本
被下载并执行完成(带有 async
或 defer
的脚本不会阻塞渲染),初始阶段就完成了。即使有超过两个同时进行的请求,浏览器现在可以根据资源的优先级和在标记中的顺序继续下载任何剩余的资源。
在上面的图表中,一旦渲染阻塞的 JavaScript
被下载并执行(粉色条),浏览器开始下载图片,即使两个 CSS
文件仍在进行中。黄色的垂直条表示 DOM
可交互(即 readystatechange
事件被触发)的时间点。
案例分析
preconnet
如果图片位于不同的域名
上,浏览器在下载文件之前需要打开到该域名的连接。
这在 WebPageTest
的图表中显示为绿色
、橙色
和洋红色
的条形图,表示在下载之前的预连接过程。
我们可以使用 preconnect
资源提示来提前开始下载图片。
在上面的图表中,在初始阶段之前,浏览器打开了与 cdn.glitch.global
域的连接,这使得浏览器能够开始下载文件。一旦浏览器退出初始阶段(黄色垂直线
),它立即开始下载图片,从而节省了约 350 毫秒的时间。
preload
preload
指令允许你向浏览器提供关于“晚发现”
(late-discovered
)的关键资源的信息。这对于在样式表或脚本中加载的资源特别有用,例如背景图片
或字体
。
在我们的示例中,图片在标记中声明并且早早被发现,因此 preload
的效果很小。(效果有,但是不多)
在上面的图表中,我们用以下内容替代了 preconnect
:
<link
rel="preload"
as="image"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
尽管使用了 preload
,但图片仍然要等到同时进行的请求少于两个时才开始下载。
预加载资源类型
这里在额外说一些,关于哪些资源可以使用prelaod
。
fetchpriority
既然perlaod
在有些场景中效果不是很好,那么我们可以另谋出路。
我们可以使用 Fetch Priority
来向浏览器指示 image-1.jpg
的优先级比默认优先级更高,使用以下方式:
<img
src="https://cdn.glitch.global/.../image-1.jpg"
fetchpriority="high"
/>
这将把图片的初始优先级从低优先级提升到高优先级,使得图片可以在初始阶段被加载。
上面的瀑布图显示了在初始阶段与其他关键资源并行加载的 image-1.jpg
。这给我们带来了迄今为止最大的改进。
将 preload 和 fetchpriority 合并
到目前为止,Fetch Priority
只在基于 Chromium
的浏览器上受支持,然而,它在不支持识别 fetchpriority
属性的浏览器上会有优雅的失败处理。这使得我们可以将 preload
指令与 Fetch Priority
结合使用。
<link
rel="preload"
as="image"
fetchpriority="high"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
支持 Fetch Priority
的浏览器将使用分配的 fetchpriority
进行预加载资源,而不支持的浏览器将使用 preload
指令进行预加载。
上面的图表显示了与之前包含在 <img>
元素上的 fetchpriority
属性的图表类似的结果。这种方法的优点在于统一了在支持 Fetch Priority
和不支持的浏览器上优先处理资源的方法。
fetchpriority 的好处
fetchpriority
的好处是指资源被发现的时间与开始下载的时间之间的差异。我将其称为机会
。因此,如果资源早早被发现,但浏览器开始下载它的时间较晚,那么机会就更大。
如果图像来自不同的域名,还可以将将建立连接的时间
包括在机会中。
总结
由于篇幅有限,关于LCP
的内容,这里先不展开,我们会单独出一篇文章。
- 将 LCP 图像托管在与 HTML 文档相同的域上。如果无法实现,请使用
preconnect
提前打开连接。 - LCP 图像应包含在文档标记中。如果无法实现,请使用
preload
告知浏览器在请求前下载图像。 - 尽量避免阻塞资源。如果 LCP 图像以低优先级下载,可以使用
fetchpriority
提示浏览器提前下载图像。
后记
分享是一种态度。
参考资料:
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。