1. 题目与适用场景
一个内容页面需要顶部阅读进度条,文章卡片进入滚动容器时还要淡入。团队先用 scroll 事件监听器和 requestAnimationFrame 实现,但在低端设备上出现主线程抖动。请使用 CSS Scroll-driven Animations 重新设计,解释 scroll progress 与 view progress 的区别,并说明浏览器不支持时的降级、reduced-motion 和真实验证方法。
2. 面试官考察点
- 是否理解滚动驱动动画把 CSS 动画绑定到 scroll timeline 或 view timeline,而非默认时间轴。
- 是否能区分 scroll() 追踪滚动容器进度与 view() 追踪元素在最近滚动容器中的可见范围。
- 是否处理命名时间轴、动画范围、overflow 条件和动画简写覆盖顺序。
- 是否把兼容性、性能、键盘阅读、减少动效和无 JavaScript 降级纳入方案。
3. 回答前需要澄清的问题
- 进度条跟随整个文档,还是跟随内部文章滚动容器?
- 卡片动画应在进入视口时播放一次,还是随可见比例正向和反向变化?
- 目标浏览器是否允许使用 animation-timeline,旧浏览器需要什么视觉和功能降级?
- reduced-motion 用户是否仍要看到进度变化,以及动画是否影响阅读顺序和焦点?
4. 30 秒回答框架
我会把阅读进度条绑定到滚动容器的 scroll progress timeline,把卡片绑定到元素的 view progress timeline。scroll() 表示滚动位置从起点到终点的进度,view() 表示元素在最近滚动容器中进入和离开可视区域的范围。动画仍由 keyframes 定义,animation-timeline 决定进度来源;必须确保容器真的可滚动,并明确动画范围。旧浏览器保留静态进度提示或使用节流后的轻量 JavaScript,不能让核心内容依赖动画。最后补上 supports、reduced-motion、键盘和低端设备测试。
5. 分步骤深入解答
第一步:确认滚动时间轴的语义
scroll progress timeline 由滚动容器的滚动位置驱动,通常从起点的 0% 走到终点的 100%。view progress timeline 由目标元素相对于最近滚动容器的可见范围驱动,适合卡片进入和离开视口时同步动画。两者都建立在 CSS Animations 和 Web Animations API 的时间轴模型上。
第二步:实现阅读进度条
先让文章容器出现实际溢出,再命名滚动时间轴,把进度条的 animation-timeline 指向同名时间轴。不要只设置 animation-timeline 而忘记 keyframes;也不要把没有滚动范围的容器误当成可用时间轴。
.article-scroller {
overflow-y: auto;
scroll-timeline-name: --article-scroll;
scroll-timeline-axis: block;
}
.reading-progress {
transform-origin: left center;
animation: grow-progress linear;
animation-timeline: --article-scroll;
}
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}第三步:实现卡片的 view timeline
卡片可以使用匿名 view(),由最近的祖先滚动容器追踪它的可见范围。需要更精确控制时,用 view-timeline-name 和 view-timeline-inset 命名时间轴并调整开始、结束边界。动画范围应与设计稿中的可见区间一致,避免卡片刚触碰边缘就完成全部动画。
.card {
animation: reveal-card linear both;
animation-timeline: view(block 15% 15%);
}
@keyframes reveal-card {
entry 0% { opacity: 0; transform: translateY(1rem); }
cover 45% { opacity: 1; transform: translateY(0); }
exit 100% { opacity: 0.6; }
}第四步:处理动画范围与简写陷阱
命名时间轴和匿名函数都需要有正确的滚动容器关系;overflow、轴向和元素层级任一不匹配都可能让时间轴没有进度。设置 animation 简写后再设置 animation-timeline,因为简写会把未写出的时间轴重置为默认值。对复杂进入、覆盖和退出阶段,使用 animation-range 或范围关键帧表达意图,并在目标浏览器实测。
第五步:设计降级与无障碍验证
用 @supports 检测 animation-timeline。增强规则不可用时,保留静态进度条、正常内容顺序和可读对比度;若确需 JavaScript fallback,只在滚动事件中做节流后的低成本更新,避免重复执行布局。prefers-reduced-motion: reduce 下移除位移和连续动效,仍保留进度信息和焦点顺序。验证键盘滚动、屏幕阅读器、快速反向滚动、嵌套滚动容器和低端设备帧率。
6. 高质量示范回答
我会把阅读进度条绑定到文章滚动容器的 scroll progress timeline,把卡片绑定到最近滚动容器的 view progress timeline。前者表示滚动位置从 0% 到 100%,后者表示元素进入、覆盖和离开可视区的范围。实现时先确认 overflow 产生真实滚动距离,再设置 keyframes 和 animation-timeline,避免 animation 简写覆盖时间轴。通过 @supports 提供静态降级,只有必要时使用节流的 JavaScript;reduced-motion 下移除位移和连续动画但保留内容、进度和焦点。最后用键盘、读屏、嵌套滚动和低端设备验证。
7. 常见错误
- 把 scroll() 和 view() 当成同一种进度 → 卡片无法按可见范围变化 → 先说明滚动位置与元素可见范围的差异。
- 容器没有溢出却声明 scroll timeline → 动画没有进度 → 检查内容尺寸、overflow 和轴向。
- 用 animation 简写覆盖了 animation-timeline → 时间轴回到默认时间轴 → 简写后重新声明 animation-timeline。
- 只在现代浏览器显示内容 → 旧浏览器功能缺失 → 保留静态进度和正常阅读顺序。
- reduced-motion 只把 opacity 设为零 → 内容反馈消失 → 去掉位移和连续动效,保留语义和可见状态。
8. 追问及应对
追问一:scroll timeline 和 view timeline 何时分别使用?
整个滚动区域的阅读进度适合 scroll timeline;单个元素进入、覆盖和离开滚动容器的动画适合 view timeline。选择依据是进度来源,而不是是否使用了 JavaScript。
追问二:为什么声明了 animation-timeline 却没有动画?
常见原因是容器没有实际滚动范围、轴向不匹配、时间轴名称不在祖先关系中,或 animation 简写后来把时间轴重置。应从布局溢出、时间轴关系和声明顺序逐项排查。
追问三:旧浏览器如何降级?
保留静态进度提示和完整内容;若产品确实需要动态进度,再用节流的 scroll 监听器做轻量更新。降级不应改变阅读顺序、键盘访问和焦点行为。
追问四:如何兼顾 reduced-motion?
在 prefers-reduced-motion: reduce 下移除位移、缩放和长时长动画,保留进度条的静态或瞬时更新、内容顺序与可读反馈,并验证快速滚动不会丢失信息。