编排磁吸式滚动、由浏览器动画化的状态过渡,并严谨地尊重 prefers-reduced-motion。
在 Kodokon 中打开本课滚动吸附涉及两个角色:容器定义一个吸附视口(它的可见区域,被 scroll-padding 收缩),并通过 scroll-snap-type 定义一种策略——proximity 只在靠近吸附点时才吸附,而 mandatory 则保证你总会落在某个吸附点上,即使在程序化滚动之后也是如此。子元素用 scroll-snap-align 声明它们的对齐区域(可通过 scroll-margin 调整)。一个鲜为人知的细节:在快速滑动时,浏览器可能会合理地跳过若干吸附点;scroll-snap-stop: always 会强制它停在每一个点上,这对于故事式的轮播至关重要。
.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(实时表现)。默认效果是交叉淡入淡出。在过渡期间页面是不可交互的。
function applyUpdate(updateDom) {
if (!document.startViewTransition) {
updateDom();
return;
}
document.startViewTransition(() => updateDom());
}真正的威力来自 view-transition-name:一个具名元素会被抽取到它自己的过渡组中。如果前后都存在一个同名元素,浏览器会自动在两者之间插值位置和尺寸——从缩略图到全屏视图的变形效果,无需一行动画 JavaScript。每个名字在任一时刻都必须在页面上唯一,否则过渡会被放弃。这些伪元素用普通的 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;
}接下来是无障碍义务:prefers-reduced-motion: reduce 表明用户对运动敏感(前庭失调、偏头痛)。减少不等于移除:一个不透明度淡入淡出仍然可以接受;必须被中和的是那些大幅度的移动、缩放和视差效果。你已经知道 CSS 中的防御性全局覆盖(接近于零的时长,已在上一个模块中讲过);视图过渡又增加了第二条战线:在 JavaScript 一侧,在启动过渡或动画化滚动之前先查询 window.matchMedia("(prefers-reduced-motion: reduce)")。
const reduceMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (reduceMotion || !document.startViewTransition) {
updateDom();
} else {
document.startViewTransition(() => updateDom());
}