轮播滑动效果是为轮播界面增添视觉趣味的好方法。当用户滚动浏览幻灯片时,每张幻灯片在进入、居中和退出滚动端口时都可以播放动画。例如,幻灯片可以淡入淡出、旋转或缩放。这能打造动态且富有吸引力的用户体验。与简单的进入/退出动画不同,此效果使用单个连续动画来控制幻灯片在整个滚动窗口中的显示。
如何实现
下面介绍了如何创建轮播幻灯片效果:
创建滚动条:此元素将用作轮播幻灯片的容器。在此示例中,它使用
overflow-x: scroll来允许水平滚动。<ul class="scroller"> <li class="entry">1</li> <li class="entry">2</li> <li class="entry">3</li> … </ul>.scroller { overflow-x: scroll; }定义动画:创建一个 CSS 动画,用于定义幻灯片在滚动视口中移动时的不同状态。您可以为动画的任何部分定义关键帧。例如,您可以通过添加
50%关键帧,为幻灯片位于滚动条中心时定义一个状态。在此示例中,scale属性使幻灯片在靠近中心时变大,在远离中心时变小。@keyframes animate { 0% { scale: 0.5; } 50% { scale: 1; } 100% { scale: 0.5; } }应用动画和
view-timeline:将动画附加到轮播界面幻灯片,并将其关联到view-timeline,以便在元素在容器中滚动时跟踪该元素。.scroller > * { animation: animate auto linear both; animation-timeline: view(inline); }默认情况下,
view()会跟踪block轴上的元素。如果您需要在inline轴上跟踪它,可以使用view(inline)。
示例代码
此代码使用匿名 view-timeline 在滚动时为水平滚动器的轮播项添加动画效果:
@keyframes animate {
0% {
scale: 0.5;
}
50% {
scale: 1;
}
100% {
scale: 0.5;
}
}
.scroller > * {
/* Applies the animation using an `auto` duration */
animation: animate auto linear both;
/* Sets the animation timeline to use an anonymous view progress timeline, tracking the element's progress through the scroller on the inline axis */
animation-timeline: view(inline);
}
以下代码使用命名的 view-timeline,在滚动时为水平滚动器的轮播项添加动画效果:
@keyframes animate {
0% {
scale: 0.5;
}
50% {
scale: 1;
}
100% {
scale: 0.5;
}
}
/* This creates a named view-timeline on each carousel item. The timeline is used to drive the animation that is applied on the same element. */
.scroller > * {
/* Applies the animation using an `auto` duration */
animation: animate auto linear both;
/* Defines a named view progress timeline, tracking the element's progress through the scroller on the inline axis */
view-timeline: --item inline;
/* Sets the animation timeline to use the named view progress timeline defined above */
animation-timeline: --item;
}
最佳做法
使用滚动驱动动画时,请务必遵循一些最佳实践,以确保提供流畅且无障碍的体验:
- 务必添加功能检测:并非所有浏览器都支持滚动驱动的动画。使用
@supports ((animation-timeline: view()) and (animation-range: entry))检查是否支持,并为不支持的浏览器提供后备方案。- 此处必须包含
(animation-range: entry)检查,以滤除仅提供部分支持的浏览器。 - 请勿将
scroll-timeline-polyfill软件包用于回退策略,因为它功能不完整,并且存在许多已知问题。 - 如果动画仅被视为装饰性元素,请选择渐进增强,不要提供后备。
- 此处必须包含
- 务必尊重用户偏好:有些用户更喜欢在网页上看到较少的动画。使用
prefers-reduced-motion媒体查询为这些用户停用或减少动画。 - 务必尝试仅为高性能 CSS 属性添加动画效果:为了获得最流畅的动画效果,请坚持为可由浏览器的合成器线程处理的属性(例如
transform和opacity)添加动画效果。为其他属性(例如width或height)添加动画效果可能会导致性能问题。 - 务必使用正确的声明顺序:使用
animation简写属性时,请在它之后声明animation-timeline和animation-range,以防止简写属性重置时间轴。
如果多个 DOM 元素需要根据同一时间轴添加动画效果,或者您需要为定义了 view-timeline 的元素的子元素添加动画效果,则最好使用命名的 view-timeline。如果您要添加动画效果的元素也是定义 view-timeline 的元素,则可以使用 view() 使用匿名视图时间轴。
使用 view() 函数创建滚动驱动的动画时:
- 可选:明确指定要跟踪的轴。如果未定位到默认的
block轴(例如在水平滚动条中),请使用view(block)或view(inline)明确指定要跟踪的轴。
使用 view-timeline 属性创建滚动驱动的动画时:
- 务必使用 CSS
<dashed-ident>作为名称(例如view-timeline: --my-custom-name) - 可选:明确指定要跟踪的轴。如果目标不是默认的
block轴(例如在水平滚动条中),请使用view-timeline-axis明确指定要跟踪的轴。 - 务必确保查找范围有效:如果声明
view-timeline的元素不是动画元素的平面树祖先,请在共享祖先上使用timeline-scope来提升view-timeline名称的可见性。
浏览器支持和后备策略
因此,通常需要采用后备策略。
对于不支持滚动驱动动画的浏览器,您可以使用后备方案来重现视觉效果。后备方案通常使用滚动监听器(用于 ScrollTimeline 效果)或 IntersectionObserver API(用于 ViewTimeline 效果)构建。
在内置支持滚动驱动动画的浏览器中,请始终使用原生 CSS 实现,因为这些实现的性能更高。
请注意,并非所有效果都可以通过后备方法重现。
具体而言,对于此用例,以下脚本会为不支持滚动驱动动画的浏览器应用回退。它使用 Web Animations API (Element.animate()) 为轮播界面中的每个项创建一个暂停的动画。然后,它会监听滚动条上的 scroll 事件,并根据列表项在滚动条中的滚动进度更新每个动画的 currentTime。
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const scroller = document.querySelector('.scroller');
const entries = document.querySelectorAll('.entry');
// Create a map to store animations
const animations = new Map();
entries.forEach(entry => {
const animation = entry.animate(
{
scale: ['0.5', '1', '0.5']
},
{
duration: 1, // We'll control the time ourselves
fill: 'both'
}
);
animation.pause();
animations.set(entry, animation);
});
// Update animations on scroll
const tick = () => {
const scrollerRect = scroller.getBoundingClientRect();
entries.forEach(entry => {
const animation = animations.get(entry);
if (!animation) return;
const entryRect = entry.getBoundingClientRect();
const progress = (entryRect.left + entryRect.width / 2 - scrollerRect.left) / scrollerRect.width;
animation.currentTime = progress;
});
};
scroller.addEventListener('scroll', tick);
tick();
}