写了三年滚动交互,今天才发现浏览器自己会认路了——@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 版本上这四个状态全部可用。

今天就可以做:

  1. 把页面里所有 scrollLeft/scrollWidth/clientWidth 比较换成 scrollable 查询
  2. 把 sticky 导航栏的吸顶样式从 IntersectionObserver 迁移到 stuck: top
  3. 把轮播图当前帧高亮从 scroll 监听换成 snapped 查询
  4. 把「向下滚隐藏导航栏」从 scroll 方向 flag 换成 scrolled 查询

scroll-state 查询家族到这里算完整了:stuck/snapped/scrollable 问的是当前状态,scrolled 问的是刚才的动作。这两个维度加在一起,滚动交互里 90% 的 JS 场景都能覆盖。

评论区

0 条评论

登录后可评论。