导航栏跟着滚动的方向自己变——CSS scroll-state() 把四种滚动状态全包了
以前做「滚动时隐藏导航,上滚显示」这种交互,第一反应就是写一个 scroll 事件监听器,然后 debounce,再判断 scrollY 的增减。代码不复杂,但问题是:每次滚动都要在主线程跑一遍 JavaScript。
Chrome 144 引入的 @container scroll-state() 终于把这件事变成纯 CSS 的活了。
核心用法
给滚动容器加一个 container-type: scroll-state,然后用 @container scroll-state() 查询滚动状态:
.scroll-container {
container-type: scroll-state;
}
/ 导航栏:下滚隐藏,上滚显示 /
.nav {
transition: transform 0.3s ease;
}
@container scroll-state(scrolled: down) {
.nav {
transform: translateY(-100%);
}
}
@container scroll-state(scrolled: up) {
.nav {
transform: translateY(0);
}
}
就这样,不需要一行 JavaScript。
四个状态查询
scroll-state() 支持四种状态,场景各不相同:
scrolled — 滚动方向和距离
/ 向左滑时把侧边栏推出去 /
@container scroll-state(scrolled: left > 50px) {
.sidebar {
transform: translateX(-100%);
}
}
/ 向下滚超过 200px 时显示「回到顶部」按钮 /
@container scroll-state(scrolled: down > 200px) {
.back-to-top {
opacity: 1;
pointer-events: auto;
}
}
stuck — 元素被卡在边缘(粘性定位)
.container {
container-type: scroll-state;
}
@container scroll-state(stuck: top) {
.sticky-header {
background: rgba(0, 0, 0, 0.9);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
}
@container scroll-state(stuck: bottom) {
.sticky-footer {
border-top: 2px solid #e74c3c;
}
}
这个在以前只能靠 IntersectionObserver 配合 getBoundingClientRect 来判断,现在一句 CSS 搞定。
snapped — 滚动吸附状态(轮播、snap 容器)
.carousel {
container-type: scroll-state;
}
@container scroll-state(snapped) {
.carousel-dot {
transform: scale(1.2);
background: #e74c3c;
}
}
scrollable — 容器是否可滚动
@container scroll-state(scrollable) {
.scroll-hint {
opacity: 1;
}
}
@container scroll-state(not scrollable) {
.scroll-hint {
display: none;
}
}
可以拿来做「内容不够长就不显示滚动提示」这类交互。
组合使用:粘性导航的完整例子
结合 stuck 和 scrolled,可以实现一个「吸顶后随滚动变色,滚动方向决定显隐」的导航栏:
.header {
container-type: scroll-state;
}
@container scroll-state(stuck: top) {
.header {
position: sticky;
top: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
}
@container scroll-state(stuck: top) and (scrolled: down) {
.header {
transform: translateY(-100%);
}
}
@container scroll-state(stuck: top) and (scrolled: up) {
.header {
transform: translateY(0);
}
}
和 scroll-driven animations 的区别
scroll-driven animations 是用滚动进度来驱动动画时间线,关心的是「滚到哪了」;scroll-state() 是查询滚动动作和状态,关心的是「滚了没、往哪滚、卡住没」。两个 API 互补,组合起来几乎可以替代所有常见的 scroll + JS 交互场景。
浏览器支持
scroll-state() 在 Chrome 144+ 和 Safari 18+ 可用。Firefox 还在跟进中,生产环境使用建议加 @supports 降级:
.header {
container-type: scroll-state;
}
@supports (container-type: scroll-state) {
/ scroll-state 用 CSS /
}
@supports not (container-type: scroll-state) {
/ 回退到 JS 方案 /
}
一个 container-type 加四个状态查询,把以前靠 JavaScript 监听 scroll 事件才能实现的交互全收进去了。主线程省了这部分计算,滚动性能自然就上去了。
评论区
登录后可评论。