做前端的小伙伴肯定都遇到过这种需求:页面滚动到某个位置,某个元素就“嗖”一下弹出来,或者图片滑动到视野中间就变大变清晰。以前要实现这效果,咱得搬出JavaScript的Intersection Observer,写一堆监听、回调,还得操心性能。但现在CSS直接给咱来了个王炸——animation-timeline属性配合view()函数,让动画直接跟元素的“露脸程度”挂钩,这就好比给动画装上了“眼力见儿”,它自个儿就知道什么时候该动、什么时候该歇。
聊聊view()是个什么神仙操作
简单讲,view()函数就像给元素装了个“可视区域感应器”。它能返回一个时间线,这个时间线不是按秒走的,而是按照元素在滚动容器里露出多少来算的。比如说,一个图片刚开始在滚动条外面,完全看不见,它可能处于动画的“未开始”状态;当它一点点滑进来,动画就跟着进度走;等它完全滑出去,动画也就结束了。
这玩意儿简直就是CSS版的Intersection Observer,只不过咱们不用写一行JS。想象一下,一个轮播图里一排图片,滑到中间的图片自动放大变清晰,两边的图片就缩小加模糊,这种效果用view()做起来,就跟写普通动画一样顺手。
手把手教你给轮子装上动画
光说不练假把式,咱直接开干,从零搭一个“滑到中间就放大变清晰”的图片轮播。
先把轮子的架子搭好
首先得有个能滚动的容器,咱就叫它.carousel,这里面放一堆图片卡片。
<main class="carousel">
<div class="carousel-slide">
<img src="pic-1.jpg" alt="第一张风景图">
</div>
<div class="carousel-slide">
<img src="pic-2.jpg" alt="第二张风景图">
</div>
<div class="carousel-slide">
<img src="pic-3.jpg" alt="第三张风景图">
</div>
<div class="carousel-slide">
<img src="pic-4.jpg" alt="第四张风景图">
</div>
</main>为了让这些卡片乖乖排成一行,并且超出部分能滚动,咱得给.carousel加上flex布局和溢出滚动。
.carousel {
display: flex;
width: max(480px, 50vw); /* 最小宽度480px,喜欢用视口一半 */
overflow-x: auto; /* 横向滚动 */
}卡片呢,咱让它固定宽度,占容器宽度的三分之一,这样一次能瞅见三个。同时为了防止被压缩,flex-shrink: 0得安排上。
.carousel .carousel-slide {
flex-shrink: 0;
width: calc(100% / 3); /* 三个卡片并排 */
aspect-ratio: 0.8; /* 高宽比,让卡片有点形状 */
}
.carousel .carousel-slide img {
width: 100%; /* 图片填满卡片 */
display: block; /* 去掉图片底下那点空白 */
}现在架子算是有了,但滚动起来有点生硬,咱可以加个滚动吸附,让每次滑动停下来时,卡片正好对准中间。
.carousel {
scroll-snap-type: x mandatory; /* 强制横向吸附 */
scroll-behavior: smooth; /* 滚动起来丝滑一点 */
}
.carousel .carousel-slide {
scroll-snap-align: center; /* 卡片吸附在容器中间 */
}这样一来,每次滑动松开,卡片就会“咔哒”一下,稳稳停在视野中央。不过这里有个小细节,如果想把滚动条藏起来,可以加个scrollbar-width: none,但得留神,火狐和Safari处理方式不一样,咱就图个简洁,不加也完全没问题。
让轮子能转起来
接下来是关键,得让卡片在滑到中间时变个样。咱先写个动画,从模糊缩小状态,变成清晰正常大小。
@keyframes slide {
/* 前45%和最后阶段保持模糊缩小状态 */
45%, 100% {
transform: scale(0.5);
filter: blur(6px) brightness(0.8);
border-radius: 20px;
}
/* 中间50%的位置清晰放大 */
50% {
transform: scale(1);
filter: none;
border-radius: 4px;
}
}这动画看着眼熟吧?就是平时写的那种。接下来把这动画绑定到每个卡片上,但咱得让动画按“露脸程度”跑,而不是按时间跑。
.carousel .carousel-slide {
animation: slide; /* 引用动画 */
animation-timeline: view(inline); /* 使用视图时间线,inline表示横向滚动 */
}就这么两行,动画就跑起来了。注意animation-timeline最好写在animation之后,不然可能被animation的默认值auto覆盖掉,导致时间线不生效。这时候去滚动轮播,就能看到卡片滑到中间时变大变清晰,滑到两边就变小变模糊,完全不用JS干预。
解决动画错位的尴尬
不过细心的可能会发现,有时候动画跑得不太对劲,比如卡片还没完全到中间就已经开始变化了,或者离开后变化太慢。这是因为view()默认的追踪范围是整个滚动视口,从元素刚露头就开始,直到完全离开才结束。
这时候可以调整view()函数里的inset参数。它就像两个“哨兵”,一个守在滚动视口起始边,一个守在结束边,用来划定动画触发的范围。比如想让动画从元素刚接触到视口下边缘开始,直到完全进入视口结束,可以这么写:
.carousel .carousel-slide {
animation: slide;
animation-timeline: view(100% 0%);
}这里的100%指的是元素从视口下方边缘(即100%露出)开始触发,0%指的是到视口上方边缘(即0%露出)结束,但实际跑起来效果正好反过来——咱想要的是元素从进入视口到离开的过程中,中间阶段才做变化,所以这个值得根据效果微调。如果看不懂,可以记住一个经验:先跑起来,再调百分比,直到动画时机对味儿。
animation-range 让控制更精准
除了inset参数,还有个叫animation-range的属性也能帮咱精确控制动画区间。比如只想让动画在元素进入视口的时候触发,可以写:
.carousel .carousel-slide {
animation: slide;
animation-timeline: view();
animation-range: entry; /* 只在进入过程中播放 */
}entry表示元素从开始进入视口到完全进入这段时间,exit则是离开的时候,cover是从开始进入直到完全离开的整个过程,contain是元素完全在视口内的时间段。这玩意儿就像给动画画了个“播放时间线”,让控制更顺手。
| 参数值 | 行为描述 |
|---|---|
| entry | 元素进入时 |
| exit | 元素离开时 |
| cover | 全程播放 |
| contain | 完全可见时 |
换种玩法,背景动起来也超带感
既然view()能让任何CSS属性跟着滚动走,那咱就可以玩点花的。比如轮播图滑到中间时,背景颜色渐变一下,或者背景图片位置来个位移。
@keyframes bgMove {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.carousel .carousel-slide {
background-image: linear-gradient(45deg, #f06, #ff0);
background-size: 200% 200%;
animation: bgMove;
animation-timeline: view(inline);
}这样每个卡片滑到中间,背景颜色就像波浪一样流动起来,视觉冲击力直接拉满。而且因为动画是挂载在每个卡片上的,每个卡片滚动时都会触发自己的背景移动,完全不会互相干扰。
最后唠叨一句,view()跟scroll()的区别得拎清楚。scroll()是跟着滚动容器的滚动进度走,比如页面滚动到一半,动画就走到一半;而view()是跟着元素自身的可见性走,更适合做逐个元素的入场、强调效果。选哪个,就看具体场景,想全局联动就用scroll(),想单个元素“显眼包”就用view()。
CSS这波操作属实把动画门槛拉低了,不用再写那些又臭又长的监听逻辑,几行样式就能搞定以前需要JS库才能实现的效果。快动手试试,给自家轮播图加点丝滑的动态,让用户滑动的时候能眼前一亮。
