Kodokon kodokon.com

现代交互:scroll-snap、视图过渡

编排磁吸式滚动、由浏览器动画化的状态过渡,并严谨地尊重 prefers-reduced-motion。

11 分钟 · 3 题

在 Kodokon 中打开本课

滚动吸附涉及两个角色:容器定义一个吸附视口(它的可见区域,被 scroll-padding 收缩),并通过 scroll-snap-type 定义一种策略——proximity 只在靠近吸附点时才吸附,而 mandatory 则保证你总会落在某个吸附点上,即使在程序化滚动之后也是如此。子元素用 scroll-snap-align 声明它们的对齐区域(可通过 scroll-margin 调整)。一个鲜为人知的细节:在快速滑动时,浏览器可能会合理地跳过若干吸附点scroll-snap-stop: always 会强制它停在每一个点上,这对于故事式的轮播至关重要。

CSS
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 1rem;
}
.carousel > article {
  flex: 0 0 80%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
一个磁吸式轮播:每一张幻灯片都会自己居中,任何滑动都无法跳过一张。

View Transitions API 无需任何库即可为 DOM 状态变化制作动画。document.startViewTransition(callback) 会捕获当前状态的视觉快照,运行你的回调(回调会改变 DOM),然后在旧状态与新状态之间制作动画。在底层,浏览器会在页面之上构建一棵伪元素树:::view-transition 包含若干 ::view-transition-group 元素,每一个都持有一个 ::view-transition-old(快照,可类比为一张图片的替换内容)和一个 ::view-transition-new(实时表现)。默认效果是交叉淡入淡出。在过渡期间页面是不可交互的。

JAVASCRIPT
function applyUpdate(updateDom) {
  if (!document.startViewTransition) {
    updateDom();
    return;
  }
  document.startViewTransition(() => updateDom());
}
渐进增强:在不支持时,更新只是直接应用,没有动画。

真正的威力来自 view-transition-name:一个具名元素会被抽取到它自己的过渡组中。如果前后都存在一个同名元素,浏览器会自动在两者之间插值位置和尺寸——从缩略图到全屏视图的变形效果,无需一行动画 JavaScript。每个名字在任一时刻都必须在页面上唯一,否则过渡会被放弃。这些伪元素用普通的 CSS 动画来设置样式。

CSS
.hero-thumb { view-transition-name: hero; }

::view-transition-old(hero),
::view-transition-new(hero) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}
具名的 hero 组被拦截,并自定义了它的动画时长。

接下来是无障碍义务:prefers-reduced-motion: reduce 表明用户对运动敏感(前庭失调、偏头痛)。减少不等于移除:一个不透明度淡入淡出仍然可以接受;必须被中和的是那些大幅度的移动、缩放和视差效果。你已经知道 CSS 中的防御性全局覆盖(接近于零的时长,已在上一个模块中讲过);视图过渡又增加了第二条战线:在 JavaScript 一侧,在启动过渡或动画化滚动之前先查询 window.matchMedia("(prefers-reduced-motion: reduce)")

JAVASCRIPT
const reduceMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (reduceMotion || !document.startViewTransition) {
  updateDom();
} else {
  document.startViewTransition(() => updateDom());
}
JavaScript 侧的安全防护:当用户要求减少运动时,更新在不做变形的情况下应用。

知识检测

确认你已牢记本课的重点内容。

  1. scroll-snap-stop: always 在轮播项目上有什么作用?
    • 它会禁用触摸滚动的惯性
    • 它会强制在每一个吸附点上停下,即使在快速滑动时也是如此
    • 它会阻止沿垂直轴方向的滚动
  2. document.startViewTransition() 在运行你的回调之前捕获了什么?
    • 当前 DOM 的深度克隆
    • 旧状态的视觉快照,在 ::view-transition-old 中重放
    • 整个页面的序列化 HTML
  3. 在从 JavaScript 启动视图过渡之前,如何尊重 prefers-reduced-motion?
    • 做不到:这个偏好只能在 CSS 中读取
    • 检测 window.matchMedia('(prefers-reduced-motion: reduce)').matches,命中时不使用 startViewTransition 直接应用更新
    • 给 startViewTransition 传入 { motion: 'reduce' } 选项