写了三年滚动交互,今天才发现浏览器自己会认路了——@container scroll-state() 把这件事彻底原生化了
每次写滚动交互,你都要写一堆 scroll 事件或者 IntersectionObserver 来判断用户滚到了哪里——今天这件事被 Chrome 一个属性彻底原生化了,连方向都记得。
CSS @container scroll-state(),Chrome 133 上线三剑客,Chrome 144 把最后一块拼图补上,2026 年的滚动状态检测从此不用一行 JS。
四种状态,彻底替代你的 scroll 监听
这个能力的核心是一个新的 container-type 值:
html {
container-type: scroll-state;
}
然后用 @container scroll-state(状态) 来查询子元素的样式。四种状态,各管各的事:
1. stuck:吸顶了没有
sticky 导航栏吸顶前后样式不一样,以前靠 IntersectionObserver 硬算,现在浏览器自己知道:
.sticky-header-wrapper {
container-type: scroll-state;
position: sticky;
top: 0;
}
@container scroll-state(stuck: top) {
.nav-bar {
max-width: 56rem;
border-radius: 0.75rem;
background: rgb(255 255 255 / 0.92);
}
}
吸顶前 edge-to-edge,吸顶后自动变圆角浮层,CSS 自己会过渡,中间零行 JS。
2. snapped:当前吸附到哪一帧了
banner 轮播图经常要突出当前项,以前要靠 scroll 事件配合 scroll-snap-offset 算,现在浏览器直接告诉你:
.horizontal-track li {
container-type: scroll-state;
}
@container scroll-state(snapped: inline) {
.card-content img {
transform: scale(1.1);
filter: sepia(0);
}
}
当前吸附帧的图片自动放大 + 去滤镜,滚动停止的瞬间浏览器就把状态更新了。
3. scrollable:还能不能往某方向滚
滚动区域两侧的箭头指示器,只有内容溢出时才显示——这以前要比较 scrollLeft、scrollWidth、clientWidth 三个值:
/* 左边还有内容可滚就显示左箭头 */
@container scroll-state(scrollable: left) {
.scroll-arrow.left { opacity: 1; }
}
/* 右边还有内容可滚就显示右箭头 */
@container scroll-state(scrollable: right) {
.scroll-arrow.right { opacity: 1; }
}
三个值比较变成一个状态查询,内容是否可滚浏览器比你知道得更准。
4. scrolled:最后一次滚向哪了(Chrome 144 新增)
这是最后一块拼图。记录用户最后一次滚动的方向,配合 translate 就能做经典的「隐藏导航栏」——向下滚时导航栏滑走,向上滚时导航栏滑回来:
/* 最后一次滚的方向是向下(底部),隐藏导航栏 */
@container scroll-state(scrolled: bottom) {
.main-header {
transform: translateY(-100%);
}
}
/* 最后一次滚的方向是向上(顶部),显示导航栏 */
@container scroll-state(scrolled: top) {
.main-header {
transform: translateY(0);
}
}
一行 transition 加上去,向下划隐藏、向上划出现,流畅得像原生 App 的体验,整个交互没有一行 JS 事件监听。
为什么说这件事把账彻底变了
关键在这句话:浏览器合成器(compositor)已经在实时计算滚动方向了。你的 JS scroll 事件和 rAF 循环,本质上是在主线程里重复计算合成器已经知道的数据。
scroll-state 查询没有增加任何新能力,它只是把合成器已经掌握的状态暴露给 CSS——减少一个冗余副本,主线程省下来的就是纯赚的。
浏览器支持现状
只有 Chromium(Chrome / Edge)支持,Safari 和 Firefox 还没上。这看起来是个坑,但它的失败模式非常友好:不支持的浏览器直接忽略整个 @container 块,你的元素保持默认状态可用。
所以渐进增强策略很简单:默认样式本身就是可用的完整状态,scroll-state 查出来的样式只是锦上添花。导航栏永远能看,只是不会自动隐藏;snap 效果永远有,只是不会自动高亮。零破坏,可直接上线。
下一步:你现在就能做的事
Chrome 144 已经 Stable 了(2026 年中 Chrome 已从 4 周改为 2 周发布周期),今天在主流 Chrome 版本上这四个状态全部可用。
今天就可以做:
- 把页面里所有 scrollLeft/scrollWidth/clientWidth 比较换成 scrollable 查询
- 把 sticky 导航栏的吸顶样式从 IntersectionObserver 迁移到 stuck: top
- 把轮播图当前帧高亮从 scroll 监听换成 snapped 查询
- 把「向下滚隐藏导航栏」从 scroll 方向 flag 换成 scrolled 查询
scroll-state 查询家族到这里算完整了:stuck/snapped/scrollable 问的是当前状态,scrolled 问的是刚才的动作。这两个维度加在一起,滚动交互里 90% 的 JS 场景都能覆盖。
评论区
登录后可评论。