配了三年长列表,每次滚动都要等那些看不见的内容渲染完——今天 CSS 自己会跳过了

写一个商品列表,1000 个卡片,首屏加载 800ms,滚动卡成 PPT。

你第一反应是装 react-window,或者找个虚拟滚动库。但其实两行 CSS 就能解决,浏览器自己会跳过屏幕外内容的渲染。

这就是 content-visibility: auto。

浏览器替省了什么

默认情况下,哪怕一个 div 滚到了屏幕外 5000px,浏览器依然会为它跑完完整的 Layout + Paint。1000 个商品卡,哪怕用户只能看见前 5 个,剩下 995 个的渲染成本全算进去了。

content-visibility: auto 做的事很简单:告诉浏览器,这个元素在视口外时可以跳过渲染,只保留 DOM 结构。滚动接近时(约视口外 1500px 预加载区),浏览器才真正把它画出来。

效果是实打实的。某电商实测数据:

  • 首次渲染耗时:860ms → 210ms
  • Layout 耗时:320ms → 45ms
  • 滚动掉帧率:12% → 2%

另一份数据:200 个带复杂阴影的卡片,LCP 从 2.8s 掉到 1.2s,内存占用从 420MB 砍到 210MB。这不是优化,是量级变化。

少了它会抽搐

只写 content-visibility: auto 是不够的,必须配合 contain-intrinsic-size。

因为离屏元素高度是 0,浏览器按 0 算滚动区域总高度。等它滚进视口再渲染、撑开真实高度——滚动条会突然变短,内容上跳,用户手指位置全乱了。

contain-intrinsic-size 就是给浏览器一个预估高度,让它提前把滚动条高度算准:

.card {
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}

contain-intrinsic-size 的 auto 关键字是精髓:浏览器会记住元素第一次渲染后的真实高度,后面再遇到同类元素直接用缓存值,不用重新算。滚动条全程稳定。

两个值,用哪个

content-visibility 还有两个取值:

  • visible:默认值,啥也不做
  • hidden:完全跳过渲染,且不保留可交互性(类似 display:none 但可恢复)

hidden 适合那些「用户根本不会看到」的内容,比如隐藏的 iframe 容器、屏幕外的固定侧栏。普通列表项用 auto 就够了。

不是虚拟滚动的替代品

content-visibility 和 react-window 这类虚拟滚动是互补关系,不是替代关系。

虚拟滚动负责控制 DOM 数量(只渲染可见区附近的 N 个节点),content-visibility 负责让「残留的那 N 个节点」渲染得更快。两者叠加,效果最好。

浏览器支持情况

Chrome 85+/Safari 15.4+/Firefox 126+,三引擎全部完成,覆盖率约 92%。主流项目可以直接用,不需要 polyfill。老旧浏览器会自动回退到全量渲染,不影响功能。

怎么落地

如果你的页面有长列表、商品卡片、瀑布流、聊天记录这类场景,先在 DevTools Performance 面板里看 Layout Shift 和 Paint 耗时——大概率能找到一堆屏幕外的元素在偷偷烧性能。

两行 CSS 加一个预估高度,今天就能把它修掉。

评论区

0 条评论

登录后可评论。