使用 CSS 隔离

CSS 隔离通过允许浏览器将页面的某个子树与页面的其余部分隔离开来,从而提高了网页的性能。如果浏览器知道页面的某一部分独立于其他内容,就可以优化渲染并提高性能。

containcontent-visibility 属性让开发者能够告知用户代理,一个元素是否应该渲染其内容,以及当它在屏幕外时是否应该渲染其内容。然后,用户代理会在适当的时候对元素应用隔离,可能会推迟布局和渲染直到需要时才进行。

本指南介绍了 CSS 隔离的基本目标,以及如何利用 containcontent-visibility 来提供更好的用户体验。

基本示例

网页通常包含多个在逻辑上相互独立的区域。CSS 隔离使得它们在渲染时可以被真正地独立对待。

例如,博客通常包含多篇文章,每篇文章都包含标题和内容,如下面的标记所示。

html
<h1>My blog</h1>
<article>
  <h2>Heading of a nice article</h2>
  <p>Content here.</p>
</article>
<article>
  <h2>Another heading of another article</h2>
  <p>More content here.</p>
</article>

使用 CSS,我们为每篇文章应用值为 contentcontain 属性。content 值是 contain: layout paint style 的简写。

css
article {
  contain: content;
}

从逻辑上讲,每篇文章都独立于页面上的其他文章。这些信息通常是创建页面的 Web 开发者所知道的,而且很可能是显而易见的。然而,浏览器不知道你的内容的意图,不能假设一篇文章或其他内容区域是完全自包含的。

这个属性提供了一种向浏览器解释这一点并明确允许其进行性能优化的方法。它告诉浏览器,该元素的内部布局与页面的其余部分完全分离,并且该元素的所有内容都在其边界内绘制。任何内容都不能在视觉上溢出。

通过在每个 <article> 上设置 contain: content,我们已经表明了这一点;我们告诉浏览器每篇文章都是独立的。然后,浏览器可以利用这些信息来决定如何渲染每个 <article> 的内容。例如,它可能不会渲染可视区域之外的文章。

当在页面末尾附加更多文章时,浏览器不需要重新计算布局或重绘之前的内容;它也不需要触及隔离元素子树之外的任何区域。但是,如果盒模型属性是相互依赖的,浏览器将需要重新计算布局和重绘。例如,如果 <article> 的样式使其大小依赖于其内容(例如,使用 height: auto),那么浏览器将需要考虑其大小的变化。

关键概念和术语

contain 的值

有四种类型的隔离:布局(layout)、绘制(paint)、尺寸(size)和样式(style)。使用 contain 属性,通过包含这些类型的任意组合,来指定要应用于元素的隔离类型。

布局隔离

css
article {
  contain: layout;
}

布局通常作用于整个文档,这意味着如果移动一个元素,整个文档都需要被当作任何地方都可能发生移动来处理。通过使用 contain: layout,你可以告诉浏览器它只需要检查这个元素——元素内部的一切都作用于该元素,不影响页面的其余部分,并且该隔离盒会建立一个独立的格式化上下文

此外

  • float 布局将在指定元素内部独立执行。
  • 外边距不会跨越布局隔离边界进行折叠。
  • 布局容器是其 absolute 定位和 fixed 定位后代的包含块
  • 该隔离盒会创建一个堆叠上下文,因此可以使用 z-index

注意:当使用 container-typecontainer-name 属性时,containstylelayout 值会自动应用。

绘制隔离

css
article {
  contain: paint;
}

绘制隔离本质上是将盒子裁剪到主盒子的内边距边缘。不会有可见的溢出。与layout 隔离相同的附加说明也适用于 paint 隔离(见上文)。

另一个优点是,如果应用了隔离的元素在屏幕外,浏览器就不需要绘制它的子元素——因为它们完全被该盒子包含,所以它们也都在屏幕外。

尺寸隔离

css
article {
  contain: size;
}

单独使用尺寸隔离在性能优化方面作用不大。然而,尺寸隔离意味着应用了尺寸隔离的元素的子元素大小不能影响元素本身的大小——其大小的计算就好像它没有子元素一样。

如果你在元素上设置了 contain: size,你需要使用 contain-intrinsic-size 或者其普通属性 contain-intrinsic-widthcontain-intrinsic-height 来指定该元素的大小。如果没有设置尺寸,在大多数情况下该元素可能会变成零尺寸。

css
article {
  contain: size;
  contain-intrinsic-size: 100vw auto none;
}

样式隔离

css
article {
  contain: style;
}

尽管名为样式隔离,但它并不提供像 Shadow DOM@scope 那样的作用域样式。style 值的主要用途是防止在一个元素中更改 CSS 计数器,从而影响到树的其余部分的情况。

使用 contain: style 可以确保 counter-incrementcounter-set 属性创建的新计数器仅作用于该子树。

你可以通过包含多个以空格分隔的值来指定多种隔离类型,例如 contain: layout paint,或者使用两个特殊值之一。

特殊值

contain 有两个特殊值,它们是前三种或所有四种隔离类型的简写。

  • content
  • strict

我们在上面的例子中遇到了第一个。使用 contain: content 会开启 layoutpaintstyle 隔离。由于它省略了 size,所以这是一个可以广泛应用的安全值。

contain: strict 声明的行为与 contain: size layout paint style 声明(包含四个以空格分隔的值)相同,提供了最强的隔离。使用它风险更大,因为它应用了 size 隔离;存在盒子因依赖其子元素大小而最终变为零尺寸的风险。

为了消除这种风险,在使用 strict 时务必设置尺寸。

css
article {
  contain: strict;
  contain-intrinsic-size: 80vw auto none;
}

以上代码与以下代码相同:

css
article {
  contain: size layout paint style;
  contain-intrinsic-size: 80vw auto none;
}

content-visibility

当你有大量内容可以从强隔离中受益,并且这些内容经常在屏幕外时——例如,如果你所有的博客文章都可以在博客主页上作为无限滚动的博客查看——可以使用 content-visibility: auto 来一次性应用所有隔离。

content-visibility 属性控制一个元素是否渲染其内容,并强制应用一组强隔离,允许用户代理潜在地省略大量的布局和渲染工作,直到需要时才进行。它使得用户代理可以跳过一个元素的渲染工作(包括布局和绘制),直到需要时才进行——这使得初始页面加载速度快得多。

其可能的值有:

  • visible:默认行为——元素的内容会像往常一样进行布局和渲染。
  • hidden:元素会跳过其内容。被跳过的内容将无法被用户代理的功能访问,例如页内查找、Tab 键顺序导航等,也无法被选中或聚焦。
  • auto:元素开启布局隔离、样式隔离和绘制隔离,就像设置了 contain: content 一样。如果该元素与用户无关,它也会跳过其内容。与 hidden 不同,被跳过的内容仍然可用于用户交互,保持可聚焦、可选择、在正常的 Tab 键顺序中,并且可用于内容内搜索。

与用户相关

用户代理有一个概念,即内容与用户相关。如果满足以下任一条件,元素就变得“与用户相关”:

  • 元素出现在视口中,或出现在用户代理定义的视口周围的边距内(视口尺寸的 50%,以便为元素可见性变化时给应用准备时间)。
  • 元素或其内容获得焦点。
  • 元素或其内容被选中,例如通过用鼠标光标拖动文本或通过其他高亮操作。
  • 元素或其内容被放置在顶层中。

当设置 content-visibility: auto,并且浏览器确定内容与用户相关时,浏览器将渲染该内容。

跳过其内容

当你在一个元素上设置 content-visibility: hidden 时,你是在告诉浏览器它与用户无关,因此其内容应该被跳过并且不被渲染。这有助于提高性能。

当一个元素上设置了 content-visibility: auto,并且浏览器确定其内容与用户相关时,浏览器也会跳过该元素的内容。

当一个元素跳过其内容时:

  • 它会开启布局、样式、绘制和尺寸隔离。
  • 其内容不会被绘制,就好像在其上设置了 visibility: hidden 一样。
  • 其内容不会接收指针事件,就好像在其上设置了 pointer-events: none 一样。

这在上述两种情况下都会发生,但是对于 content-visibility: auto,内容可以被搜索、接收焦点,并以其他方式从不相关变为相关。而对于 content-visibility: hidden 则不是这样。

注意:要为从 content-visibility: hidden 到可见值的过渡添加动画,你需要设置 transition-behavior: allow-discrete@starting-style 样式。更多信息请参阅displaycontent-visibility 的过渡

另见