有关如何细分 LCP 并确定关键改进领域的分步指南。
发布时间:2020 年 4 月 30 日
Largest Contentful Paint (LCP) 是 3 项 Core Web Vitals 指标之一,表示网页主要内容的加载速度。具体而言,LCP 衡量的是从用户发起网页加载到视口中渲染最大图片或文本块所用的时间。
为了提供良好的用户体验,网站应力求至少有 75% 的网页访问的 LCP 不超过 2.5 秒。
很多因素都会影响浏览器加载和渲染网页的速度,而其中任何环节出现的延迟都会对 LCP 产生重大影响。
对网页的某个部分进行快速修复,很少能显著缩短 LCP 用时。若要提高 LCP,您必须查看整个加载过程,并确保优化其中的每个步骤。
了解 LCP 指标
在优化 LCP 之前,开发者应先了解自己是否存在 LCP 问题,以及此类问题的严重程度。
LCP 可以通过多种工具进行衡量,但并非所有工具都会以相同的方式衡量 LCP。要了解真实用户的 LCP,我们应该关注真实用户的体验,而不是 Lighthouse 或本地测试等基于实验室的工具显示的内容。这些实验室工具可以提供丰富的信息来解释 LCP 并帮助您提高 LCP,但请注意,仅实验室测试可能无法完全代表实际用户的体验。
基于真实用户的 LCP 数据可通过以下两种方式显示:在网站上安装的真实用户监控 (RUM) 工具,或者通过 Chrome 用户体验报告 (CrUX)(针对数百万个网站从真实 Chrome 用户处收集匿名数据)。
使用 PageSpeed Insights CrUX LCP 数据
PageSpeed Insights 在标记为了解真实用户的体验的顶部部分提供了 CrUX 数据。您可以在标记为诊断性能问题的底部部分找到更详细的实验室数据。如果您的网站有 CrUX 数据,应始终先关注真实用户数据。
PageSpeed Insights 最多会显示四种不同的 CrUX 数据:
- 此网址的移动数据
- 此网址的桌面设备数据
- 整个来源的移动流量
- 整个 Origin 的桌面设备数据
您可以在此部分顶部和右上角的控件中进行切换。如果某个网址的数据不足以在网址一级显示,但有源数据,PageSpeed Insights 始终会显示源数据。
整个来源的 LCP 可能与具体网页的 LCP 有很大不同,具体取决于相应网页上的 LCP 加载方式(与该来源中的其他网页相比)。也可能受访问者导航到这些网页的方式的影响。首页往往由新用户访问,因此往往是“冷”加载的,没有任何缓存内容,因此网站上速度最慢的网页往往也是如此。
查看四种不同类别的 CrUX 数据有助于您了解 LCP 问题是特定于此网页,还是更常见的网站级问题。同样,它还可以显示哪些类型的设备存在 LCP 问题。
使用 PageSpeed Insights CrUX 补充指标
想要优化 LCP 的广告客户还应使用 First Contentful Paint (FCP) 和 Time to First Byte (TTFB) 计时,它们是很好的诊断指标,可提供有关 LCP 的宝贵数据洞见。
TTFB 是访问者开始导航到网页(例如,点击链接)的时间,直到接收到 HTML 文档的前几字节。较高的 TTFB 可能会使实现 2.5 秒的 LCP 变得具有挑战性,甚至是无法实现。
TTFB 偏高的原因包括:多个服务器重定向、访问者距离最近的网站服务器较远、访问者网络状况不佳,或因查询参数而导致无法使用缓存内容。
网页开始呈现后,可能会先进行初始绘制(如背景颜色),然后显示某些内容(如网站标题)。初始内容的外观由 FCP 进行衡量。FCP 与其他指标之间的差值非常有价值。
如果 TTFB 和 FCP 之间存在较大差异,则可能表示浏览器需要下载大量会阻塞渲染的素材资源。这也表明网站必须完成大量工作才能呈现任何有意义的内容 - 这是一个非常依赖客户端呈现的网站的典型标志。
FCP 和 LCP 之间存在较大差异表示 LCP 资源无法立即供浏览器优先处理(例如,文本或图片由 JavaScript 管理,而不是在初始 HTML 中提供),或者浏览器正在完成其他工作,然后才能显示 LCP 内容。
使用 PageSpeed Insights Lighthouse 数据
PageSpeed Insights 的“Lighthouse”部分提供了一些有关如何改进 LCP 的指南,但首先您应检查所提供的 LCP 是否与 CrUX 提供的真实用户数据大致一致。如果 Lighthouse 和 CrUX 不一致,CrUX 可能会提供更准确的用户体验。在对网页执行操作之前,请确保您的 CrUX 数据是针对网页的,而不是完整来源。
如果 Lighthouse 和 CrUX 都显示 LCP 值需要改进,Lighthouse 部分可以提供有关如何改进 LCP 的有价值的指导。使用 LCP 过滤条件可仅显示与 LCP 相关的审核,如下所示:
除了改进建议之外,诊断信息可能还会提供更多信息,帮助您诊断问题。Largest Contentful Paint 元素诊断会显示构成 LCP 的各种时间的实用细分:
我们将在下一部分中深入探讨这些子部分。
LCP 细分维度
如果 PageSpeed Insights 无法提供如何提高此指标的答案,那么针对 LCP 进行优化可能会是一项较为复杂的任务。对于复杂的任务,通常最好将其分解为更易于管理的较小任务,并分别解决每个任务。
本部分介绍了如何将 LCP 细分为其最重要的子部分,然后介绍了有关如何优化每个部分的具体建议和最佳实践。
大多数网页加载通常包含许多网络请求,但为了发现改善 LCP 的机会,您应先从以下两个方面入手:
- 初始 HTML 文档
- LCP 资源(如果适用)
虽然网页上的其他请求可能会影响 LCP,但这两个请求(具体而言是 LCP 资源的开始和结束时间)可以揭示您的网页是否针对 LCP 进行了优化。
要识别 LCP 资源,您可以使用开发者工具(例如前面介绍的 PageSpeed Insights、Chrome 开发者工具或 WebPageTest)来确定 LCP 元素。然后,您可以在网页加载的所有资源的网络广告瀑布流中匹配该元素加载的网址(如果适用)。
例如,下图直观地展示了典型网页加载的网络瀑布图中突出显示的这些资源,其中 LCP 元素需要发出图片请求才能呈现。
为了让网页得到充分优化,您应该让 LCP 资源请求尽早开始加载,并希望 LCP 元素在 LCP 资源完成加载后尽快呈现。为了直观了解特定网页是否遵循此原则,您可以将总 LCP 时间细分为以下子部分:
- 至第一字节的时间 (TTFB)
- 从用户发起网页加载到浏览器收到 HTML 文档响应的第一个字节的时间。
- 资源加载延迟
- 从 TTFB 到浏览器开始加载 LCP 资源所用的时间。如果 LCP 元素不需要加载资源即可呈现(例如,如果该元素是使用系统字体呈现的文本节点),则此时间为 0。
- 资源加载时长
- 加载 LCP 资源本身所用的时长。如果 LCP 元素不需要加载资源即可渲染,此时为 0。
- 元素渲染延迟
- 从 LCP 资源完成加载到 LCP 元素完全渲染之间的时间。
每个网页的 LCP 都由以下四个子类别组成。它们之间没有间隔或重叠,并且它们的总和就是完整的 LCP 时间。
每个网页的 LCP 值都可以细分为这四个子部分。这两者之间不存在重叠或间断。这些时间加起来等于完整的 LCP 时间。
优化 LCP 时,尝试单独优化这些子部分会很有帮助。但同样重要的是,您需要对所有这些元素进行优化。在某些情况下,对某个部分应用的优化不会改善 LCP,只会将节省的时间转移到另一个部分。
例如,在前面的网络广告瀑布流中,如果您通过更高压缩率或改用更优化的格式(例如 AVIF 或 WebP)来缩减图片的文件大小,这会缩短资源加载时长,但实际上不会改善 LCP,因为时间只会转移到元素呈现延迟子部分:
出现这种情况的原因是,在此网页上,LCP 元素会一直处于隐藏状态,直到 JavaScript 代码加载完毕,然后所有内容都会同时显示。
此示例有助于说明,您需要优化所有这些子部分,才能取得最佳 LCP 结果。
最佳子部分时间
为了优化 LCP 的每个子部分,您必须了解在经过充分优化的网页上,这些子部分的理想细分是什么。
在四个子部分中,有两个子部分包含“延迟”一词。这表明您希望这些时间尽可能接近零。另外两个部分涉及网络请求,而网络请求本身就需要时间。
请注意,这些时间细分可作为参考,而非严格的规定。如果网页的 LCP 时间始终在 2.5 秒内,那么相对比例如何并不重要。但是,如果您在任一“延迟”部分花费了大量不必要的时间,则很难持续达到 2.5 秒的目标。
考虑 LCP 时间细分的一种好方法是:
- 绝大部分 LCP 时间都应花在加载 HTML 文档和 LCP 源代码上。
- 如果这两个资源在 LCP 之前有任何一个未加载,就有机会改进。
如何优化各个部分
至此,您已经了解了在充分优化的网页上应如何细分各个 LCP 子部分的时间,接下来可以开始优化自己的网页了。
接下来的四部分将介绍有关如何优化各个部分的建议和最佳做法。这些建议会按顺序显示,首先显示最有可能产生最大影响的优化建议。
1. 消除资源加载延迟
此步骤的目标是确保 LCP 资源尽早开始加载。虽然在理论上,资源最早可以在 TTFB 后立即开始加载,但在实践中,浏览器实际开始加载资源之前总会有一些延迟。
一般来说,LCP 资源应与该网页加载的第一个资源同时开始加载。或者,换言之,如果开始加载 LCP 资源的时间晚于第一个资源,就表示仍有改进空间。
一般来说,有两个因素会影响 LCP 资源的加载速度:
- 发现资源时。
- 资源的优先级。
在发现资源时进行优化
为确保您的 LCP 资源尽早开始加载,请务必在浏览器的预加载扫描器在初始 HTML 文档响应中发现该资源。例如,在以下情况下,浏览器可以通过扫描 HTML 文档响应来发现 LCP 资源:
- LCP 元素是
<img>
元素,其src
或srcset
属性存在于初始 HTML 标记中。 - LCP 元素需要 CSS 背景图片,但该图片是通过 HTML 标记中的
<link rel="preload">
(或使用Link
标头)预加载的。 - LCP 元素是一个文本节点,需要 Web 字体才能呈现,并且该字体是在 HTML 标记中使用
<link rel="preload">
(或使用Link
标头)加载的。
下面是一些无法通过扫描 HTML 文档响应发现 LCP 资源的示例:
- LCP 元素是使用 JavaScript 动态添加到网页的
<img>
。 - 使用隐藏其
src
或srcset
属性(通常作为data-src
或data-srcset
)的 JavaScript 库延迟加载 LCP 元素。 - LCP 元素需要 CSS 背景图片。
在上述每种情况下,浏览器都需要先运行脚本或应用样式表(这通常涉及等待网络请求完成),然后才能发现 LCP 资源并开始加载该资源。这绝不是最佳做法。
为了消除不必要的资源加载延迟,您的 LCP 资源应可从 HTML 源中发现。如果资源仅从外部 CSS 或 JavaScript 文件引用,则应以较高的提取优先级预加载 LCP 资源,例如:
<!-- Load the stylesheet that will reference the LCP image. -->
<link rel="stylesheet" href="/path/to/styles.css">
<!-- Preload the LCP image with a high fetchpriority so it starts loading with the stylesheet. -->
<link rel="preload" fetchpriority="high" as="image" href="/path/to/hero-image.webp" type="image/webp">
优化为资源指定的优先级
即使可通过 HTML 标记发现 LCP 资源,该资源可能无法像第一个资源那样开始加载。如果浏览器预加载扫描器的优先级启发法无法识别相应资源很重要,或者如果它确定其他资源更重要,就会发生这种情况。
例如,如果您在 <img>
元素上设置 loading="lazy"
,则可以使用 HTML 延迟 LCP 图片。使用延迟加载意味着,只有在布局确认图片位于视口内后,系统才会加载资源,因此加载时间可能会比以往晚一些。
即使不采用延迟加载,浏览器也不会以最高优先级初始加载图片,因为图片不是渲染阻塞资源。您可以为可能受益于更高优先级的资源使用 fetchpriority
属性,向浏览器暗示哪些资源最重要:
<img fetchpriority="high" src="/path/to/hero-image.webp">
如果您认为 <img>
元素可能是网页的 LCP 元素,不妨为其设置 fetchpriority="high"
。不过,如果为多张或两张以上的图片设置高优先级,则优先级设置对缩短 LCP 没有帮助。
您也可以降低可能在文档响应早期阶段显示但因样式设置而不可见的图片(例如,轮播幻灯片中的图片在启动时不显示)的优先级:
<img fetchpriority="low" src="/path/to/carousel-slide-3.webp">
降低某些资源的优先级可以为需要更多资源的资源提供更多带宽,但请务必谨慎。始终在开发者工具中检查资源优先级,并使用实验室和现场工具测试更改。
优化 LCP 资源优先级和发现时间后,您的网络广告瀑布流应如下所示(LCP 资源与第一个资源同时启动):
2. 消除元素渲染延迟
此步骤的目标是确保 LCP 元素在其资源加载完成后(无论何时)能够立即呈现。
LCP 元素在其资源完成加载后无法立即呈现的主要原因是呈现由于某种其他原因而被阻止:
- 由于
<head>
中的样式表或同步脚本仍在加载,因此整个网页被阻止呈现。 - LCP 资源已完成加载,但尚未将 LCP 元素添加到 DOM(正在等待加载一些 JavaScript 代码)。
- 元素被一些其他代码(例如仍在确定用户应参与的实验的 A/B 测试库)隐藏。
- 主线程因长时间运行的任务而被阻塞,渲染工作需要等到这些长时间运行的任务完成。
以下各部分介绍了如何解决造成元素渲染延迟不必要的最常见问题。
减少或内嵌阻止渲染的样式表
从 HTML 标记加载的样式表会阻止渲染其后面的所有内容,这很好,因为您通常不希望渲染未设置样式的 HTML。不过,如果样式表非常大,以致于加载时间远远超过 LCP 资源所需要的时间,那么就会阻止 LCP 元素呈现 - 即使其资源加载完毕后也是如此,如下例所示:
要解决此问题,您可以执行以下任一操作:
- 将样式表内嵌到 HTML 中,以避免产生额外的网络请求;或者
- 减小样式表的大小。
一般来说,建议仅在样式表较小的情况下内联样式表,因为 HTML 中的内联内容无法从后续网页加载的缓存中受益。如果样式表太大,其加载时间比 LCP 资源更长,则不太适合内嵌。
在大多数情况下,确保样式表不会阻止 LCP 元素渲染的最佳方式是缩减其大小,使其小于 LCP 资源。这应该可以确保它不会成为大多数访问的瓶颈。
以下是关于缩减样式表大小的一些建议:
- 移除未使用的 CSS:使用 Chrome 开发者工具查找未在使用且有可能被移除(或延迟)的 CSS 规则。
- 推迟非关键 CSS:将样式表拆分为初始网页加载所需的样式,以及可以延迟加载的样式。
- 缩减和压缩 CSS:对于重要的样式,请确保尽可能减小其传输大小。
延迟或内嵌会阻止渲染的 JavaScript
您几乎从来没必要将同步脚本(没有 async
或 defer
属性的脚本)添加到网页的 <head>
中,这样做几乎总是会对性能产生负面影响。
如果 JavaScript 代码需要在网页加载时尽早运行,则最好将其内嵌,以免渲染延迟等待其他网络请求。不过,与样式表一样,只有在脚本非常小的情况下,您才能内嵌脚本。
<head> <script src="/path/to/main.js"></script> </head>
<head> <script> // Inline script contents directly in the HTML. // IMPORTANT: only do this for very small scripts. </script> </head>
使用服务器端呈现
服务器端呈现 (SSR) 是指在服务器上运行客户端应用逻辑,并使用完整的 HTML 标记响应 HTML 文档请求的过程。
从优化 LCP 的角度来看,SSR 有两大主要优势:
- 他人可通过 HTML 源代码发现您的图片资源(如前面的第 1 步中所述)。
- 您的网页内容无需额外 JavaScript 请求完成即可呈现。
SSR 的主要缺点是需要额外的服务器处理时间,这可能会降低 TTFB。这种权衡通常是值得的,因为服务器的处理时间由您控制,而用户的网络和设备功能则不在您的控制范围内。
与 SSR 类似的选项称为静态网站生成 (SSG) 或预渲染。这是在构建步骤(而不是按需)生成 HTML 页面的过程。如果您的架构支持预渲染,通常这是提高性能的更好选择。
拆分冗长的任务
即使您采纳了之前的建议,并且您的 JavaScript 代码不会阻塞渲染,也不负责渲染元素,它仍然会延迟 LCP。
出现这种情况的最常见原因是网页加载了需要在浏览器的主线程中解析和执行的大型 JavaScript 文件。这意味着,即使您的图片资源已下载完毕,也可能需要等到不相关的脚本执行完毕后才能呈现。
目前,所有浏览器都在主线程上渲染图片,这意味着任何阻塞主线程的操作都可能会导致不必要的元素渲染延迟。
3. 缩短资源加载时长
此步骤的目的是减少通过网络将资源的字节传输到用户设备所用的时间。通常,有三种方法可以实现这一目的:
- 缩减资源的大小。
- 减少资源传输的距离。
- 减少网络带宽争用。
- 完全消除网络时间。
缩减资源大小
网页的 LCP 资源(如果有)将是图片或网页字体。以下指南详细介绍了如何缩减这两者的大小:
减少资源传输的距离
除了缩减资源的大小,您还可以通过让服务器在地理位置上尽可能靠近用户来缩短加载时间。最佳方式是使用内容分发网络 (CDN)。
图片 CDN 尤其有用,因为它们不仅可缩短资源必须传输的距离,而且通常会缩减资源的大小 - 会自动实施之前为您提供的所有大小缩减建议。
减少网络带宽争用
即使您减少了资源的大小并缩短了必须移动的距离,如果您同时加载许多其他资源,该资源的加载时间仍然可能较长。此问题称为网络争用。
如果您为 LCP 资源分配了较高的 fetchpriority
并尽早开始加载该资源,则浏览器会尽力阻止优先级较低的资源与其竞争。不过,如果您要加载许多 fetchpriority
较高的资源,或者只是一般地加载大量资源,则可能会影响 LCP 资源的加载速度。
完全消除网络时间
缩短资源加载用时的最佳方法是从流程中完全消除网络。如果您使用高效的缓存控制政策提供资源,那么再次请求这些资源的访问者将从缓存中获取这些资源,从而将资源加载时长几乎缩短为零!
如果您的 LCP 资源是 Web 字体,除了缩减 Web 字体大小之外,您还应考虑是否需要在 Web 字体资源加载时阻止渲染。如果您将 font-display
值设置为 auto
或 block
以外的任何值,则文本将在加载期间始终可见,并且 LCP 不会因额外的网络请求而被阻塞。
最后,如果您的 LCP 资源较小,那么可以将资源内嵌为数据网址,同时避免额外的网络请求。不过,使用数据网址需要注意,因为这样就无法缓存资源,在某些情况下,还会因额外的解码开销而导致更长的呈现延迟时间。
4. 缩短加载第一个字节所需时间
此步骤的目标是尽快提供初始 HTML。此步骤列在最后,是因为开发者对此步骤的控制力度通常最小。不过,这也是最重要的步骤之一,因为它会直接影响后续的每个步骤。在后端传送第一个字节的内容之前,前端不会执行任何操作,因此您可以采取任何提高 TTFB 的措施,也会改善所有其他负载指标。
导致网站 TTFB 较慢的一个常见原因是,访问者是通过多次重定向(例如通过广告或缩短链接)到达的。请始终尽量减少访问者必须等待的重定向次数。
另一个常见原因是,缓存的内容无法从 CDN 边缘服务器使用,并且所有请求必须一直定向回源服务器。如果访问者使用独特网址参数进行分析,就可能出现这种情况,即使系统本身并不会产生不同的网页也是如此。
如需有关优化 TTFB 的具体指导,请参阅优化 TTFB 指南。
在 JavaScript 中监控 LCP 细分
您可以通过组合使用以下性能 API,在 JavaScript 中获取之前讨论的所有 LCP 子部分的时间信息:
在 JavaScript 中计算这些时间值的好处是,您可以将它们发送给分析提供程序,或将其记录到开发者工具中,以帮助进行调试和优化。
例如,以下屏幕截图使用 User Timing API 中的 performance.measure()
方法向 Chrome 开发者工具“Performance”面板中的“Timings”轨迹添加条形。
“计时”轨道中的可视化图表与“网络”和“主线程”轨道并排显示时,会特别有用,因为您可以一目了然地看到页面上在这些时间范围内的其他活动。
除了在时间轨迹中直观呈现 LCP 子部分之外,您还可以使用 JavaScript 计算每个子部分在总 LCP 时间中的百分比。根据这些信息,您可以判断您的网页是否符合上文所述的建议的百分比细分维度。
此屏幕截图显示了一个示例,该示例记录了每个 LCP 子部分的总时间,以及该部分在总 LCP 时间中所占的百分比。
这两个可视化图表均使用以下代码创建:
const LCP_SUB_PARTS = [
'Time to first byte',
'Resource load delay',
'Resource load duration',
'Element render delay',
];
new PerformanceObserver((list) => {
const lcpEntry = list.getEntries().at(-1);
const navEntry = performance.getEntriesByType('navigation')[0];
const lcpResEntry = performance
.getEntriesByType('resource')
.filter((e) => e.name === lcpEntry.url)[0];
// Ignore LCP entries that aren't images to reduce DevTools noise.
// Comment this line out if you want to include text entries.
if (!lcpEntry.url) return;
// Compute the start and end times of each LCP sub-part.
// WARNING! If your LCP resource is loaded cross-origin, make sure to add
// the `Timing-Allow-Origin` (TAO) header to get the most accurate results.
const ttfb = navEntry.responseStart;
const lcpRequestStart = Math.max(
ttfb,
// Prefer `requestStart` (if TOA is set), otherwise use `startTime`.
lcpResEntry ? lcpResEntry.requestStart || lcpResEntry.startTime : 0
);
const lcpResponseEnd = Math.max(
lcpRequestStart,
lcpResEntry ? lcpResEntry.responseEnd : 0
);
const lcpRenderTime = Math.max(
lcpResponseEnd,
// Use LCP startTime (the final LCP time) because there are sometimes
// slight differences between loadTime/renderTime and startTime
// due to rounding precision.
lcpEntry ? lcpEntry.startTime : 0
);
// Clear previous measures before making new ones.
// Note: due to a bug, this doesn't work in Chrome DevTools.
LCP_SUB_PARTS.forEach((part) => performance.clearMeasures(part));
// Create measures for each LCP sub-part for easier
// visualization in the Chrome DevTools Performance panel.
const lcpSubPartMeasures = [
performance.measure(LCP_SUB_PARTS[0], {
start: 0,
end: ttfb,
}),
performance.measure(LCP_SUB_PARTS[1], {
start: ttfb,
end: lcpRequestStart,
}),
performance.measure(LCP_SUB_PARTS[2], {
start: lcpRequestStart,
end: lcpResponseEnd,
}),
performance.measure(LCP_SUB_PARTS[3], {
start: lcpResponseEnd,
end: lcpRenderTime,
}),
];
// Log helpful debug information to the console.
console.log('LCP value: ', lcpRenderTime);
console.log('LCP element: ', lcpEntry.element, lcpEntry.url);
console.table(
lcpSubPartMeasures.map((measure) => ({
'LCP sub-part': measure.name,
'Time (ms)': measure.duration,
'% of LCP': `${
Math.round((1000 * measure.duration) / lcpRenderTime) / 10
}%`,
}))
);
}).observe({type: 'largest-contentful-paint', buffered: true});
您可以按原样将此代码用于本地调试,也可以修改该代码,将这些数据发送给分析服务提供商,从而更好地了解您的网页上真实用户的 LCP 详情。
使用 Chrome 开发者工具监控 LCP 故障
Chrome DevTools 会实时记录 LCP 时间、LCP 元素以及这四个子部分,以便您查看此细分数据。
摘要
LCP 非常复杂,其时间可能会受到多种因素的影响。不过,如果您认为优化 LCP 主要是为了优化 LCP 资源的加载,那么事情就会变得简单得多。
概括来讲,优化 LCP 可以分为以下 4 个步骤:
- 确保 LCP 资源尽早开始加载。
- 确保 LCP 元素可在其资源完成加载后立即渲染。
- 在不牺牲质量的情况下,尽可能缩短 LCP 资源的加载时间。
- 尽快提供初始 HTML 文档。
如果您能够在网页上执行这些步骤,那么您一定确信自己能为用户提供最佳的加载体验,而且应该会在您的实际 LCP 得分中看到这一点。