content-visible:可提升渲染性能的新 CSS 属性

通过跳过屏幕外内容的呈现,缩短初始加载时间。

Vladimir Levin
Vladimir Levin

在 Chromium 85 中推出的 content-visibility 属性可能是在提高网页加载性能方面影响最大的新 CSS 属性之一。content-visibility 可让用户代理跳过元素的呈现工作(包括布局和绘制),直到需要这些信息为止。由于会跳过渲染,因此如果大部分内容不在屏幕范围内,利用 content-visibility 属性可显著加快初始用户的加载速度。此外,它还可以加快与屏幕上的内容的互动。这挺好看的。

包含代表广告网络成效的图表的演示
在我们的文章演示中,将 content-visibility: auto 应用于分块内容区域可在初始加载时将渲染性能提升 7 倍。请阅读下文了解详情。

浏览器支持

浏览器支持

  • 85
  • 85
  • 124

来源

content-visibility 依赖于 CSS 包含规范中的基元。虽然目前仅 Chromium 85 支持 content-visibility(对于 Firefox 而言,此规范被视为“值得设计原型”),但大多数现代浏览器都支持包含规范。

CSS 包含

CSS 包含的核心主要目标是通过可预测的 DOM 子树与页面的其余部分隔离,提高 Web 内容的渲染性能。

基本上,开发者可以告知浏览器将网页的哪些部分封装为一组内容,使浏览器无需考虑子树之外的状态。知道哪些内容(子树)包含隔离的内容意味着浏览器可以针对网页渲染做出优化决策。

CSS 包含有 4 种类型,每种都是 contain CSS 属性的可能值,这些值都可以合并到一个以空格分隔的值列表中:

  • size:元素的尺寸包含能够确保无需检查其后代即可布置该元素的框。这意味着,如果我们只需要元素的大小,我们可以跳过后代的布局。
  • layout:布局包含意味着子级不会影响页面上其他框的外部布局。这样一来,如果我们只想布置其他框,就可以跳过后代的布局。
  • style:样式包含可确保可能会对不仅仅对其后代产生影响的属性(例如计数器)不会对该元素进行转义。这样一来,如果我们只需要计算其他元素的样式,就可以跳过针对后代的样式计算。
  • paint:Paint 包含关系可确保包含框的后代不会显示在其边界之外。任何内容都不能明显溢出元素,如果某个元素在屏幕外或因其他原因不可见,其后代也将不可见。这样一来,当元素在屏幕外时,我们就可以跳过绘制后代的操作。

使用 content-visibility 跳过渲染工作

可能很难确定要使用哪些包含值,因为只有在指定了适当的值集时,浏览器优化才会启动。您可以随意调整这些值以查看哪种方法效果最好,也可以使用另一个名为 content-visibility 的 CSS 属性自动应用所需的包含设置。content-visibility 可确保您获得浏览器所能提供的最大性能提升,同时让开发者只需极少的工作量。

content-visibility 属性接受多个值,但使用 auto 可以立即提升性能。具有 content-visibility: auto 的元素获得 layoutstylepaint 包含性。如果该元素在屏幕外(与用户无关 - 相关元素是指其子树中具有焦点或选择的元素),它也会获得 size 包含权限(并停止对其内容进行绘制命中测试)。

这是什么意思?简而言之,如果元素在屏幕外,其后代就不会呈现。浏览器在不考虑元素的任何内容的情况下确定元素的大小,然后就停止了。系统会跳过大部分渲染,例如元素子树的样式和布局。

当元素接近视口时,浏览器不再添加 size 容器,并开始对元素的内容进行绘制和点击测试。这使得渲染工作能够及时完成,以供用户看到。

无障碍功能注意事项

content-visibility: auto 的一个特性是,屏幕外的内容在文档对象模型中仍然可用,因此也是无障碍树(与 visibility: hidden 不同)。这意味着,用户可以在页面上搜索并导航到内容,而无需等待内容加载或影响渲染性能。

不过,具有 display: nonevisibility: hidden 等样式特征的 landmark 元素在屏幕外时也会出现在无障碍树中,因为浏览器只有在进入视口后才会呈现这些样式。为防止这些内容在无障碍树中显示(从而可能造成混乱),请务必同时添加 aria-hidden="true"

示例:旅游博客

在此示例中,我们在右侧为旅游博客设置基准,并将 content-visibility: auto 应用于左侧的分块区域。结果会显示初始网页加载时的呈现时间从 232 毫秒30 毫秒

旅游博客通常包含一系列故事和几张图片,以及一些描述性文字。下面是在典型浏览器中导航到旅游博客时会发生的情况:

  1. 系统会从网络下载页面的一部分,同时下载所有所需资源。
  2. 浏览器会对页面中的所有内容进行样式和布局,而不考虑这些内容对用户是否可见。
  3. 浏览器返回第 1 步,直到下载完所有页面和资源。

在第 2 步中,浏览器会处理所有内容,以查找可能已更改的内容。它会更新所有新元素的样式和布局,以及可能因新更新而发生变化的元素。这就是渲染工作这需要时间。

一个旅游博客的屏幕截图。
一个旅游博客示例。请参阅 Codepen 上的演示

现在考虑一下,如果对博客中的每个报道添加 content-visibility: auto 会发生什么。大体循环是相同的:浏览器会下载并呈现网页块。但是,区别在于它在第 2 步中完成的工作量。

借助 content-visibility,它会设置对用户当前可见的所有内容(它们在屏幕上)的样式和布局。不过,在处理完全位于屏幕之外的故事时,浏览器会跳过渲染工作,仅设置元素框本身的样式和布局。

加载此页面的效果就像包含全屏故事和每个屏幕外故事的空白框一样。这样做的性能要好得多,预计加载的渲染成本可降低 50% 或更多。在示例中,呈现时间从 232 毫秒提升到了 30 毫秒。性能是原来的 7 倍

您需要完成哪些工作才能获得这些好处?首先,我们将内容分成几个部分:

使用 CSS 类将内容分成多个部分且带注解的屏幕截图。
通过应用 story 类将内容分成若干部分的示例,以接收 content-visibility: auto。请参阅 Codepen 上的演示

然后,我们将以下样式规则应用于这些版块:

.story {
  content-visibility: auto;
  contain-intrinsic-size: 1000px; /* Explained in the next section. */
}

使用 contain-intrinsic-size 指定元素的自然尺寸

为了实现 content-visibility 的潜在好处,浏览器需要应用大小包含机制,以确保内容的渲染结果不会以任何方式影响元素的大小。也就是说,该元素的布局方式与空元素一样。如果元素未在常规块布局中指定高度,则其高度将为 0。

这可能并不理想,因为滚动条的大小会发生变化,依赖于每个故事的高度非零。

幸运的是,CSS 还提供了另一个属性 contain-intrinsic-size,该属性可以有效地指定元素的自然尺寸(如果元素受到尺寸包含性的影响)。在我们的示例中,我们将其设置为 1000px,作为版块的高度和宽度估算值。

这意味着它的布局方式就像有一个“固有尺寸”维度的子项,从而确保未调整大小的 div 仍会占用空间。contain-intrinsic-size 充当占位符尺寸,代替呈现的内容。

在 Chromium 98 及更高版本中,contain-intrinsic-size 新增了一个 auto 关键字。指定后,浏览器将记住上次呈现的尺寸(如果有),并使用该尺寸而不是开发者提供的占位符尺寸。例如,如果您指定了 contain-intrinsic-size: auto 300px,则元素在开始时将在每个维度中使用 300px 固有尺寸,但在渲染元素的内容后,它将保留所渲染的固有尺寸。系统也会记住所有后续渲染尺寸更改。实际上,这意味着,如果您滚动已应用 content-visibility: auto 的元素,然后将其滚动回屏幕以外,它将自动保持理想的宽度和高度,而不会还原为占位符大小。此功能对于无限滚动条尤为有用,无限滚动条现在可以在用户浏览网页时自动改进大小估计值。

使用 content-visibility: hidden 隐藏内容

如果您希望无论内容在屏幕上是否显示,都保持不渲染内容,同时利用缓存渲染状态的优势,该怎么办?输入:content-visibility: hidden

content-visibility: hidden 属性可为您提供与未渲染内容和缓存渲染状态相同的优势,这与 content-visibility: auto 在屏幕外执行的操作相同。不过,与 auto 不同,它不会自动开始在屏幕上呈现。

这赋予您更多控制力,让您能够隐藏元素的内容,并在稍后快速取消隐藏它们。

将其与隐藏元素内容的其他常用方法进行比较:

  • display: none:隐藏元素并销毁其渲染状态。这意味着,取消隐藏元素的开销与渲染具有相同内容的新元素一样。
  • visibility: hidden:隐藏元素并保持其渲染状态。这不会真正从文档中移除该元素,因为它(及其子树)仍然占据页面上的几何空间,并且仍然可以被点击。它还会在需要时随时更新渲染状态,即使处于隐藏状态也是如此。

另一方面,content-visibility: hidden 会隐藏元素,同时保持其渲染状态,因此,如果需要发生任何更改,则只有在相应元素再次显示时才会发生(即移除 content-visibility: hidden 属性)。

content-visibility: hidden 的一些很好的用例包括实现高级虚拟滚动条和测量布局。它们也非常适合单页应用 (SPA)。非活跃应用视图可以保留在 DOM 中,并应用 content-visibility: hidden 以防止其显示,但会保留其缓存状态。这样一来,当视图再次处于活动状态时,就可以快速渲染该视图。

对下一次绘制的交互的影响 (INP)

INP 是一个指标,用于评估网页能否可靠地响应用户输入。响应速度可能会受到主线程上发生的过多工作(包括渲染工作)的影响。

只要能减少任意给定页面上的渲染工作,主线程便有机会更快地响应用户输入。这包括渲染工作,以及在适当的情况下使用 content-visiblity CSS 属性可以减少渲染工作,尤其是在启动期间,此时大部分渲染和布局工作都已经完成。

减少渲染工作会对 INP 产生直接影响。当用户尝试与正确使用 content-visibility 属性的页面互动时,系统会延迟屏幕外元素的布局和渲染,从而赋予主线程响应用户可见的关键工作的机会。在某些情况下,这样做可以提高网页的 INP。

总结

content-visibility 和 CSS Containment 规范意味着您的 CSS 文件将带来一些激动人心的性能提升。如需详细了解这些属性,请查看: