题干与适用场景
面试题:一个页面有吸顶标题、横向 snap 卡片和“还有内容可滚动”的提示。你会如何判断何时改变样式?请比较 CSS scroll-state 查询与 JavaScript 监听 scroll 事件的方案,并说明浏览器不支持时怎么办。
适用边界是 CSS 只负责声明式样式变化,例如吸顶时加阴影、元素 snap 后高亮、仍可向下滚动时显示提示。业务状态、复杂手势、精确滚动进度和需要向服务端上报的事件仍应由 JavaScript 处理。MDN 将 scroll-state 查询分为 scrollable、scrolled、snapped 和 stuck 四类。
面试官在考察什么
面试官看重你能否先按状态类型选 API,再处理支持矩阵和无障碍约束。强回答会指出查询容器必须设置 container-type,响应样式写在其后代元素上;也会说明不应把 CSS 状态查询误当作通用事件系统。
普通回答只背出 @container 语法。强回答还会解释何时仍需 JavaScript、如何用 @supports 降级、如何避免动画违背 prefers-reduced-motion,以及如何通过真实 sticky、snap 和溢出场景验证。
回答前需要澄清的问题
先确认目标是样式反馈还是业务逻辑。如果只是阴影、颜色、提示显隐,CSS 足够;如果需要记录用户滚动方向、触发网络请求或驱动复杂状态机,就需要 JavaScript。
再确认状态来源:元素是否真的使用 position sticky,列表是否设置 scroll-snap-type,滚动容器是否存在可见溢出。没有这些前提,对应查询会一直返回 false。
最后确认浏览器基线和动效要求。Chrome for Developers 说明 Chrome 133 引入 scroll-state 容器查询;生产方案应先定义不依赖新特性的默认样式,再把增强规则包进 supports,并尊重 reduced motion。
30 秒回答框架
可以这样回答:
“我先把需求分成样式状态和业务事件。吸顶、snap 和是否可滚动属于浏览器能判断的状态,我会给相关元素设置 container-type: scroll-state,用 @container ... scroll-state(...) 修改后代样式。若需要统计、精确进度或跨组件业务动作,仍用 JavaScript。默认样式先可用,再用 @supports 渐进增强,并在 prefers-reduced-motion 下关闭非必要动画。最后用真实滚动、snap、溢出和不支持浏览器验证。”
分步骤深入解答
先建立 scroll-state 查询容器
被查询的元素要声明 scroll-state 容器,可选命名以避免页面有多个容器时误匹配:
.sticky-heading {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: heading;
}响应样式必须放在容器的后代,不能把同一元素既作为查询容器又作为响应目标。Chrome 文档明确展示了这种代理层关系。
按状态选择 descriptor
stuck 判断 sticky 元素是否贴住边缘;snapped 判断元素是否即将吸附到 snap 容器;scrollable 判断指定方向是否还有可滚动溢出;scrolled 判断最近一次滚动方向。四者回答的问题不同,不能用一个状态替代另一个。
例如吸顶时给内部导航加阴影:
@container heading scroll-state(stuck: top) {
.nav {
box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
}
}把样式目标放到后代
如果把背景直接改在带 container-type 的元素上,代码结构容易违反查询容器与目标分离的规则。保留一个内部 wrapper,让它响应状态;这样组件可以复用,也更容易测试状态切换。
用 scrollable 做可发现性提示
对横向卡片可以在仍能向 inline-end 滚动时显示渐变提示:
.carousel {
container-type: scroll-state;
container-name: cards;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
@container cards scroll-state(scrollable: inline-end) {
.next-hint {
opacity: 1;
}
}提示必须是装饰或辅助发现,不要只靠颜色传递“还有内容”;保留键盘可滚动、焦点可见和文本标签。
用 snapped 反馈当前卡片
snapped 只在存在 scroll snap 祖先和有效 snap 轴时有意义。高亮当前卡片可以改善视觉反馈,但不要把它当作可靠的业务选择提交信号。真正的选中事件仍应由可访问控件和 JavaScript 状态管理。
用 scrolled 处理方向反馈
顶部工具栏可根据最近滚动方向显隐,但方向反馈容易造成认知负担。默认先保持工具栏可访问,在有明确收益时才通过 scrolled: block-start 或 block-end 改变位置,并加过渡。
渐进增强与降级
基础样式应在不支持 scroll-state 的浏览器中仍可操作:
.nav {
box-shadow: none;
}
@supports (container-type: scroll-state) {
.sticky-heading {
container-type: scroll-state;
}
@container scroll-state(stuck: top) {
.nav {
box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
}
}
}若体验必须在旧浏览器一致,可用 IntersectionObserver 或 ResizeObserver 做有限 fallback,但不要在 fallback 中复制整套滚动事件循环。
动效和验证
Chrome 建议把非必要动效放在 prefers-reduced-motion: no-preference 内。验证时分别测试顶部、底部、无溢出、键盘操作、触控滚动、snap 中间态、缩放和不支持查询的浏览器;检查状态变化不会阻挡焦点或内容。
高质量示范回答
“我会先问这是样式反馈还是业务事件。对吸顶阴影、snap 高亮和滚动提示,我会给 sticky 或 snap 元素设置 container-type: scroll-state,按需求使用 stuck、snapped 或 scrollable 查询,让后代元素响应。若要上报滚动、计算进度或提交选择,仍由 JavaScript 负责。默认 CSS 先保证可用,再用 @supports 增强;不支持时保持无阴影或始终显示提示,并在 reduced-motion 下减少位移。测试覆盖真实溢出、snap、键盘和旧浏览器。”
常见错误
把 scroll-state 当成 scroll 事件
错误表现:用查询直接触发埋点、请求或业务提交。失败原因:CSS 查询只表达样式条件,没有事件载荷和可靠业务时序。修正方法:样式由 CSS 管理,业务动作由语义控件和 JavaScript 管理。
忘记声明查询容器
错误表现:直接写 @container scroll-state(stuck: top),却没有 container-type: scroll-state。失败原因:浏览器没有可评估的状态来源。修正方法:先检查 sticky、snap 或溢出前提,再声明命名容器。
把响应规则写在容器本身
错误表现:同一元素同时承担 container-type 和状态样式目标。失败原因:查询容器针对后代,结构会让规则不匹配或难以维护。修正方法:增加内部 wrapper,把状态反馈放在后代。
用颜色替代可访问反馈
错误表现:只改变颜色表示当前 snap 卡片或还有更多内容。失败原因:颜色不足以传达状态,也可能影响低视力用户。修正方法:提供文本、焦点、按钮或可操作的滚动提示。
无视支持与动效边界
错误表现:假设所有浏览器都支持,并在滚动时强制位移。失败原因:旧浏览器体验退化,动效可能引发不适。修正方法:@supports 做增强、默认样式保持可用,并尊重 reduced-motion。
追问及应对
如果需要精确滚动百分比呢?
scroll-state 只能回答离散状态,不能提供连续百分比。使用 JavaScript 读取滚动范围并节流更新,但把读取、计算和写入分离,避免每个 scroll 事件同步触发布局。
如果 sticky 查询一直为 false 呢?
逐项检查 sticky 元素的 containing block、top 或 inset 是否设置、祖先是否有不合适的 overflow,以及元素是否真的进入粘滞区。确认前提后再检查 container-type 和后代选择器。
如果 snap 高亮闪烁呢?
检查 snap 轴、scroll-snap-align 和目标尺寸,避免把过渡写在会频繁改变布局的属性上。高亮只表示视觉状态,保留明确的焦点和选择状态。
如果旧浏览器必须有同样效果呢?
先定义可用的静态样式,再用小范围 IntersectionObserver 或 ResizeObserver 补充关键反馈。不要复制一套复杂的滚动监听逻辑;如果增强成本超过收益,接受无阴影或始终可见提示。
如果用户开启 reduced motion 呢?
保留信息层级,不强制位移或缩放。可以只改变阴影、边框或静态颜色,并让 prefers-reduced-motion 覆盖过渡和动画规则。