我看不到任何有用的内容!为什么加载需要这么长时间? 😖
导致用户体验不佳的一大因素是用户在看到任何内容在屏幕上完成渲染前所需的时间。First Contentful Paint 首次内容绘制 (FCP) 测量初始 DOM 内容完成渲染所需的时间,但该指标无法捕获页面上的最大(通常也更有意义)内容完成渲染所需的时间。
最大内容绘制 (LCP) 是核心 Web 指标中的一项指标,用于测量可视区域中最大内容元素变为可见的时间点。该项指标可用于确定页面主要内容在屏幕上完成渲染的时间点。
导致 LCP 不佳的最常见原因是:
浏览器从服务器接收内容所需的时间越长,在屏幕上渲染任何内容所需的时间就越长。更快的服务器响应速度能直接改善包括 LCP 在内的各项页面加载指标。
您首先需要做的是改进服务器处理内容的方式和位置。请使用Time to First Byte 首字节时间 (TTFB) 来测量您的服务器响应时间。您可以通过多种不同的方式来改进您的 TTFB:
您是否正在运行需要消耗大量时间和系统资源才能完成的查询?或者服务器端是否有其他复杂的操作会延迟页面内容的返回过程?分析和提高您服务器端代码的效率将直接改善浏览器接收数据所需的时间。
许多服务器端网络框架并不会在浏览器请求时立即提供静态页面,而是需要动态创建网络页面。换句话说,框架并不会在浏览器请求时发送一个已经准备好的完整 HTML 文件,而是需要运行逻辑来构建页面。这可能是由于数据库查询结果待定,甚至是因为组件需要由用户界面框架(例如React)生成为标记。在服务器上运行的许多网络框架都有性能指导,您可以利用这些指导来加快此过程。
内容分发网络 (CDN) 是分布在许多不同位置的服务器网络。如果您将网页内容托管在单个服务器上,那么对于地理位置较远的用户来说,您的网站加载速度就会变慢,因为他们的浏览器请求不得不跨越千山万水。您可以考虑使用 CDN 来确保您的用户永远不必为发送到远距离服务器的网络请求而等待。
如果您的 HTML 是静态的,且不需要针对每个请求进行更改,那么缓存可以防止网页进行不必要的重建。通过在磁盘上存储已生成 HTML 的副本,服务器端缓存可以减少 TTFB 并最大限度地减少资源使用。
根据您所使用的工具链,有许多不同的方法可以进行服务器缓存:
安装好的 Service Worker 会在浏览器后台运行,并可以拦截来自服务器的请求。此级别的程序化缓存控制使得缓存部分或全部 HTML 页面内容得以实现,并且只会在内容发生更改时更新缓存。
下方的图表显示使用该模式能够减少网站的 LCP 分布:
该图表显示了过去 28 天内某个网站的 LCP 分布,并且按 Service Worker 状态进行了细分。请注意,在 Service Worker 中引入优先使用缓存的 HTML 页面服务后,大多数页面加载的 LCP 值都变得更为迅速(图表的蓝色部分)。
对第三方域的服务器请求也会影响 LCP,尤其是当浏览器需要这些请求来在页面上显示关键内容的情况下。使用rel="preconnect"
来告知浏览器您的页面打算尽快建立连接。
<link rel="preconnect" href="https://example.com" />
您还可以使用dns-prefetch
来更快地完成 DNS 查找。
<link rel="dns-prefetch" href="https://example.com" />
尽管两种提示的原理不同,但对于不支持preconnect
的浏览器,可以考虑将dns-prefetch
做为后备。
<head>
…
<link rel="preconnect" href="https://example.com" />
<link rel="dns-prefetch" href="https://example.com" />
</head>
签名交换 (SXG) 是一种交付机制,通过提供采用了易于缓存格式的内容来实现更快的用户体验。具体来说, Google 搜索会缓存 SXG,有时也会预获取 SXG。对于通过 Google 搜索获得大部分流量的网站,SXG 可以是改进 LCP 的重要工具。如需了解更多信息,请参阅签名交换。
浏览器在能够渲染任何内容之前,需要将 HTML 标记解析为 DOM 树。如果 HTML 解析器遇到任何外部样式表(<link rel="stylesheet">
)或同步 JavaScript 标签(<script src="main.js">
),则会暂停解析。
脚本和样式表都是阻塞渲染的资源,这些资源会使 FCP 延迟,进而导致 LCP 延迟。延迟加载任何非关键的 JavaScript 和 CSS,从而提高网页主要内容的加载速度。
通过以下操作确保您的网站上只有最少量的必要 CSS 会阻塞渲染:
为了更加易于阅读,CSS 文件可以包含空格、缩进或注释等字符。这些字符对于浏览器来说都不是必要的,而对这些文件进行削减能够确保将这些字符删除。最终,在减少了阻塞渲染的 CSS 数量后,充分渲染页面主要内容所需的时间 (LCP) 也总是能够相应地缩短。
如果您使用模块打包器或构建工具,那么可以在其中包含一个相应的插件来在每次构建时削减 CSS 文件:
使用 Chrome 开发者工具中的代码覆盖率选项卡查找您网页上任何未使用的 CSS。
优化方式:
rel="preload"
和on load
。通过把用于首屏内容的任何关键路径 CSS 直接包括在<head>
中来将这些 CSS 进行内联。
将重要样式进行内联后,就不再需要通过往返请求来获取关键 CSS。延迟加载其余部分可以最大限度地减少 CSS 阻塞时间。
如果您无法为您的网站手动添加内联样式,请使用库来将该过程自动化。一些示例:
下载并向用户提供尽可能少的必要 JavaScript。减少阻塞渲染的 JavaScript 数量能够让渲染速度更快,从而获得更好的 LCP。
这可以通过优化您的脚本来实现,有如下几种不同的方式:
虽然 CSS 或 JavaScript 阻塞时间的增加会直接导致性能下降,但加载许多其他类型资源所需的时间也会影响绘制时间。影响 LCP 的元素类型为:
<img>
元素<svg>
元素内的<image>
元素<video>
元素(使用封面图像测量 LCP)url()
函数(而非使用 CSS 渐变)加载的带有背景图像的元素如果在首屏渲染,加载这些元素所需的时间将对 LCP 产生直接影响。有几种方法可以确保尽快加载这些文件:
对于许多网站来说,在页面加载完毕后,图像会是视图中的最大元素。这种情况的常见示例包括首图、大型轮播或横幅图像。
改善这些类型的图像进行加载和渲染所需的时间将直接提升 LCP 的速度。实现方式:
有时,在某个 CSS 或 JavaScript 文件中声明或使用的重要资源可能会比您所期望的要晚一点被获取,例如深藏在应用程序众多 CSS 文件中的某个字体。
如果您知道某个特定资源应该被优先获取,请使用<link rel="preload">
来更加及时地获取该资源。 多种类型的资源都可以进行预加载,但您应该首先侧重于预加载关键资产,例如字体、首屏图像或视频,以及关键路径 CSS 或 JavaScript。
<link rel="preload" as="script" href="script.js" />
<link rel="preload" as="style" href="style.css" />
<link rel="preload" as="image" href="img.png" />
<link rel="preload" as="video" href="vid.webm" type="video/webm" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
从 Chrome 73 开始,预加载可以与响应式图像一起使用,将两种模式相结合能够实现更快速的图像加载。
<link
rel="preload"
as="image"
href="wolf.jpg"
imagesrcset="wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w"
imagesizes="50vw"
/>
压缩诸如 Gzip 和 Brotli 之类的算法可以显著缩减在服务器和浏览器之间传输的文本文件(HTML、CSS、JavaScript)大小。所有浏览器都有效支持 Gzip,而 Brotli 几乎可以在所有较新的浏览器中使用,并能提供更好的压缩结果。
压缩您的资源将最大限度地减少这些资源的交付大小、缩短加载时间,从而改善 LCP。
当加载构成页面主要内容的资源时,根据用户的设备或网络条件按需获取不同的资源会是一个有效做法。您可以使用网络状况 API、设备内存 API 和硬件并发 API 来实现这一做法。
如果您有对初始渲染十分关键的大型资产,那么您可以根据用户的连接或设备采用同一资源的不同变体。例如,对于任何低于 4G 的连接速度,您可以显示图像,而不是视频:
if (navigator.connection && navigator.connection.effectiveType) {
if (navigator.connection.effectiveType === '4g') {
// 加载视频
} else {
// 加载图像
}
}
您可以使用的一系列实用属性:
navigator.connection.effectiveType
:有效连接类型navigator.connection.saveData
:启用/禁用数据保护程序navigator.hardwareConcurrency
:CPU 核心数navigator.deviceMemory
:设备内存Service Worker 可用于完成许多有用的任务,其中包括本文前面提到的提供较小的 HTML 响应。Service Worker 还可用于缓存任何静态资源,并在收到重复请求时将资源直接提供给浏览器,而无需通过网络。
使用 Service Worker 预缓存关键资源可以显著减少资源加载时间,特别是对于使用较弱连接重新加载网页(甚至离线访问)的用户。与自己编写自定义 Service Worker 来更新预缓存资产相比,诸如 Workbox 这样的库可以使整个过程更加容易。
许多网站使用客户端 JavaScript 逻辑直接在浏览器中渲染页面。诸如 React、Angular 和 Vue 这类的框架和库使构建单页应用变得更加容易,这些单页应用完全在客户端(而不是在服务器)中处理网页的各个层面。
如果您正在搭建一个主要在客户端进行渲染的网站,那么您应该特别小心网站在使用大型 JavaScript 包时可能对 LCP 产生的影响。如果您没有通过优化来加以阻止,那么在所有关键 JavaScript 完成下载和执行前,用户可能都无法看到页面上的任何内容或与之交互。
在搭建客户端渲染的网站时,请考虑以下优化:
如果您网站上的内容只有在一定数量的 JavaScript 完成下载后才变得可见或可以与之交互:尽可能缩减您的代码包的大小就变得尤为重要。这可以通过以下方式实现:
请回到减少 JavaScript 阻塞时间部分,阅读有关这些优化的更多信息。
对于主要由客户端渲染的网站来说,首先需要关注的始终应该是将 JavaScript 的数量最小化。但是,您还应该考虑结合服务端渲染体验来尽可能地改善 LCP。
这个概念的实现方式是使用服务器将应用渲染为 HTML,然后客户端将所有 JavaScript 及所需数据"水合"到相同的 DOM 内容中。这个做法可以通过确保页面的主要内容首先在服务器上进行渲染(而不是仅在客户端上进行渲染)来改进 LCP,但该做法有一些弊端:
预渲染是一种独立的技巧,该技巧比服务端渲染简单,并且还提供了一种改进应用程序 LCP 的方法。无头浏览器是一种没有用户界面的浏览器,我们会用无头浏览器在搭建期间生成每个路由的静态 HTML 文件。然后可以将这些文件与应用程序所需的 JavaScript 包一起进行运送。
在使用预渲染后,TTI 仍然会受到负面影响,但服务器响应时间不会像服务端渲染解决方案(仅在接到请求后才对各个页面进行动态渲染)中那样受到很大影响。
许多工具都可以用于测量和调试 LCP:
灯塔 6.0 支持在实验室环境中测量 LCP。
Chrome 开发者工具中性能面板的时间点部分包括一个 LCP 标记,并会在您将鼠标悬停在相关节点字段上时显示哪些元素与 LCP 相关联。
Chrome 用户体验报告提供在域级聚合下的真实 LCP 值