资讯文章 资讯 · NEWS
几百个条目的长列表:content-visibility 省下的渲染时间,和它带来的四个副作用
2026-10-24 约 2,053 字 预计阅读 5 分钟 yiyunkj
几
目录站最典型的页面就是一长串卡片:一个分类三百个条目,一页铺五十到一百个。首屏其实只容得下十几个,但浏览器在首屏渲染时,默认要把下面那些卡片也算一遍。
content-visibility: auto 就是为这个场景准备的。它告诉浏览器:这块内容暂时不在视口附近,布局和绘制可以先跳过,等它靠近了再补上。

图片来源:网络,版权归原作者所有,侵权请联系删除
它到底跳过了什么
按 W3C CSS Containment Module Level 2 的描述,content-visibility: auto 是「对 containment 的一次升级」:它一次性把布局、样式、绘制等几种 containment 全应用上,同时暗示浏览器可以跳过子树的工作。它跟 visibility: hidden 不一样,跟 display: none 更不一样。
最关键的一点是:它不会把跳过的内容从无障碍树里摘掉。规范里写得很明确,屏幕阅读器、页面内查找(Ctrl+F)这类工具仍然能接触到这些内容。这一点对 SEO 很重要——DOM 里的内容还在,爬虫拿到的 HTML 也还是完整的。
而 content-visibility: hidden 是另一回事。它更接近「带 containment 的 visibility: hidden」,内容不渲染,而且可能被移出无障碍树。这个属性值不要用在正文内容上,只适合缓存那些暂时不用的面板。
必须配一个占位尺寸
这是最容易踩的一坑。元素跳过内容渲染时,会同时应用尺寸 containment——浏览器把它当成没有子元素来算高度。如果你不告诉它该占多高,它就会塌成接近零。
结果是:滚动条长度一直变、滚动位置乱跳、从搜索结果点进来的锚点落在错误位置、Cmd+F 找到的内容定位偏掉。所以要配 contain-intrinsic-size:
.item-card {
content-visibility: auto;
contain-intrinsic-size: auto 180px;
}
加上 auto 关键字之后,浏览器会记住这个元素上次渲染时的真实尺寸,之后再用这个记忆值当占位,比写死一个数字稳得多。180px 只是首屏还没渲染过时的兜底估计值。
如果父级是固定行高的 grid,或者元素本身有明确 height,那就可以不写这个属性——浏览器已经知道该占多少。
四个副作用
- sticky 失效。任何祖先元素设了
content-visibility: auto,里面的position: sticky子元素就会相对这个容器定位,而不是视口。分类页那种「侧边导航跟着滚」的效果会直接不生效。修法是把 sticky 元素移到设了该属性的容器之外。 - 会被裁掉溢出。它带的 paint containment 会把绘制裁到 padding box。卡片里的下拉菜单、tooltip、超出卡片边界的阴影都会被切掉。有这类浮层的需求就别在这个容器上用。
- 多出层叠上下文。每个 containment 边界都会建立层叠上下文和包含块。页面里几十个卡片各建一个,z-index 的调试会明显变麻烦,fixed 定位的子元素可能突然锚到卡片上而不是视口。
- 打印与老浏览器。较老的 Chromium 构建在打印时会把未渲染的区块留空,稳妥做法是在
@media print里把它改回visible。Firefox 在 109 到 124 之间还需要手动开启开关,125 起才默认可用,跨浏览器场景记得用@supports包一层。
JS 那边的注意事项
还有一个不太显眼但很影响收益的点:对跳过的子树调用 getBoundingClientRect()、getComputedStyle() 这类需要布局信息的接口,会强制浏览器提前渲染它,性能收益直接归零。如果你的卡片上有滚动监听、曝光埋点之类的逻辑,检查一下有没有这类调用。
反过来,规范也提醒不要走另一个极端:被跳过的内容在语义上依然是相关的,不代表你可以无限期不更新它。规范里专门说明,状态变化事件应该用来降低更新优先级,而不是彻底跳过更新——因为辅助技术在元素被跳过时仍然会读取它。
目录站该怎么用
按粒度来分,适用性是递减的:
- 适合:单个条目卡片、列表里的重复单元、页面下方那些很重但基本静态的区块(比如「友情链接」「全部分类」)。
- 要小心:带下拉筛选器的工具栏、带 tooltip 的卡片、里面有浮层的模块。
- 不要用:页面级的外层 wrapper、包着主导航或 JSON-LD 结构化数据的容器、很小的行内元素。
粒度上有个原则:用在单个卡片上,而不是整个列表容器上。套在整个 <ul> 上,只要有一个卡片进入视口,整块都得渲染,等于没优化。套在每个 <li> 上,才是逐项跳过。
收益怎么看
这个属性省的是渲染时间,不是网络时间。如果你的页面瓶颈是 TTFB 或者图片太大,加了它不会有感觉。判断方法很直接:用 DevTools 的 Performance 面板录一次,看「Rendering」和「Layout」占了多少。这两块占比高、DOM 节点数也多的页面,才是它的目标场景。
验证的时候记得对比首屏渲染时间而不是总加载时间,同时在移动端尺寸下再测一次。