配了三年长列表,每次滚动都要等那些看不见的内容渲染完——今天 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 加一个预估高度,今天就能把它修掉。
评论区
登录后可评论。