← 📜 Scroll · 滚动怎么动、长什么样、装得下多少 / scroll-state:让样式读出容器此刻的滚动态 待审核 7 / 9
scroll-state · 容器查询

scroll-state:让样式读出容器此刻的滚动态

一个 position: sticky 的导航栏有没有正贴住顶边、一个 carousel 当前吸附在哪张、一个滚动框还能不能继续往下滚——这些「滚动态」过去只能靠 JS 监听 scroll / IntersectionObserver 反复计算。Scroll-State 容器查询(CSS Conditional Rules Level 5)把它们做成声明式的:给容器声明 container-type: scroll-state,后代就能用 @container scroll-state(...) 按状态换样式,零脚本。

1 · 三种可查询的状态

查询 问的是 取值
stuck 这个 sticky 容器当前贴住了哪条边(还是已被滚走、不再吸附)。 none / top / bottom / left / right / block-start
snapped 作为 snap-area 的这个容器,是否正是滚动容器当前吸附到的那个,在哪条轴上。 none / x / y / block / inline / both
scrollable 这个滚动容器朝某个方向还有没有可滚的内容(用来做边缘渐隐提示)。 none / top / bottom / left / right / block-start

关键点:container-type: scroll-state 设在被查询的那个容器上(sticky 元素 / snap-area / 滚动框本身),而 @container scroll-state(...) 命中的样式作用于它的后代——和普通容器查询一样,容器自身不能直接被自己的查询命中。scroll-state 不像 size 那样建立尺寸 containment,因此不影响布局

2 · stuck:sticky 贴住顶边时变身

下框里的导航栏是 position: sticky。向下滚动让它贴住顶边,看它从「圆角软底」收紧成「贴顶横条」——纯 CSS,不监听 scroll:

stuck 的 CSS 骨架:

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

@container nav scroll-state(stuck: top) {
  .nav-inner {
    background: var(--accent);
    border-radius: 10px;
    color: #fff;
  }
}

3 · snapped:吸附到正中那张就点亮

横向 carousel 里每张卡片既是 snap-area、又声明了 container-type: scroll-state。滚动容器吸附到哪张,哪张就用 scroll-state(snapped: inline) 把自己放大点亮——和 CSS Animation 的 view() 版轮播异曲同工,但这里靠的是「吸附态」而非「视口进度」:

snapped 的 CSS 骨架:

.card {
  scroll-snap-align: center;
  container-type: scroll-state;
  container-name: card;
}

@container card scroll-state(snapped: inline) {
  .card-inner {
    transform: scale(1.12);
    background: var(--accent);
  }
}

4 · scrollable:还有内容可滚时,才显示边缘渐隐

长列表上下两端的渐隐遮罩是经典的「这里还有更多」提示。过去要算 scrollTop 决定显隐,现在 scrollable: top / scrollable: bottom 直接回答「这个方向还有没有可滚内容」——滚到顶,顶部遮罩自动消失;滚到底,底部遮罩消失:

scrollable 的 CSS 骨架:

.list {
  overflow-y: auto;
  container-type: scroll-state;
  container-name: list;
}
.fade { position: sticky; opacity: 0; transition: opacity .2s; }
.fade.top { top: 0; }
.fade.bot { bottom: 0; }

@container list scroll-state(scrollable: top)    { .fade.top { opacity: 1; } }
@container list scroll-state(scrollable: bottom) { .fade.bot { opacity: 1; } }

5 · 边界与降级

  • scroll-state() 容器查询较新(Chrome 133+),需做渐进增强:不支持时容器查询整条不命中,元素保留默认样式——上面三个 demo 在旧浏览器里仍可正常滚动,只是少了贴顶变身 / 点亮 / 渐隐这层反馈。
  • stuck 要求容器本身是 position: sticky;snapped 要求容器是某个 scroll-snap 容器的 snap-area;scrollable 则查询容器作为滚动容器自身的可滚余量。三者各有前提,设错前提则永不命中。
  • 它和 scroll-snapscrollsnapchange 事件互补:事件给 JS 一个回调(拿到吸附的子元素),scroll-state 则纯在样式层完成,不进 JS。