你以为 sticky 元素只能靠 JS 判断状态?今天 Chrome 用一行属性把这件事彻底原生化了

写过前端的人都踩过这个坑——想知道一个 sticky 元素是不是真的吸顶了、想知道轮播当前停在哪个 item、想知道滚动方向是往上还是往下……最后统统靠 JS:addEventListener(‘scroll’) + 一段 IntersectionObserver + 一个 sentinel div,然后 classList.toggle。CSS 从头到尾就是个被动的执行者。

这件事,今天被 @container scroll-state() 彻底变了。

Chrome 133(2025 年 2 月)稳定支持,@container 家族的新成员,核心就干一件事:让 CSS 直接读滚动容器的状态。三个描述符覆盖你 90% 的场景:

  • stuck: top / right / bottom / left——position: sticky 的元素当前粘在哪个边缘
  • snapped: x / y / inline / block——当前 snap 到哪个 target
  • scrollable: top / right / bottom / left——在给定方向上还有没有内容可滚动
  • scrolled: top / right / bottom / left——最近一次滚动方向(Chrome 144+,2026 年 1 月)

具体怎么用?两步。

第一步,在滚动容器(或 sticky 元素本身)上声明 container-type: scroll-state:

.header-wrap {
  container-type: scroll-state;
  container-name: header;
  position: sticky;
  top: 0;
}

第二步,用 @container 查状态,给子元素套样式:

/* sticky 吸顶时,给阴影 */
@container header scroll-state(stuck: top) {
  .header-content {
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
  }
}

一个 sentinel div 都不用写,浏览器原生知道答案。

轮播场景同理——焦点项高亮、非焦点项降低亮度:

.carousel { overflow-x: auto; scroll-snap-type: x mandatory; }
.carousel-item { scroll-snap-align: center; container-type: scroll-state; }
.carousel-item > * { transition: opacity 0.3s; }
.carousel-item > * { opacity: 0.35; }
@container scroll-state(snapped: x) {
  .carousel-item > * { opacity: 1; }
}

注意一个坑:@container 只能查子元素,不能给容器自己套样式——视觉变化要落在容器内部某个子节点上。

scrollable 的典型场景:列表底部「往下滚动」提示,有内容时显示、滚动到底后消失:

.list { container-type: scroll-state size; overflow-y: auto; }
.list-hint { opacity: 0; transition: opacity 0.3s; }
@container scroll-state(scrollable: bottom) {
  .list-hint { opacity: 1; }
}

性能差异是本质:JS scroll 监听每帧都 fire 一次,主线程开销;scroll-state 查询是渲染引擎在布局阶段顺带算的,零额外开销。

浏览器支持:Chrome 133+ / Edge 133+,Safari 和 Firefox 尚未支持,全球覆盖率约 52%。建议渐进增强:先写好 fallback 样式,再用 @supports (container-type: scroll-state) { } 包增强层。

从 IntersectionObserver 到 scroll-state,是 CSS 从「被动的值」变成「主动的状态源」的又一次扩张。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 15 阅读