最近在项目中碰到一个比较典型的3d
轮播图动效,如下所示
说难不难,说简单也不简单,这是一个无限循环的轮播效果,并且个数是固定的,你会如何实现呢?
原效果是通过vue
的transition
组件实现的,感觉有些笨重,思考了一番,发现纯 CSS
也能实现这样的效果,而且性能更好,实现也能简洁,一起来看看吧
一、CSS 动画实现思路
首先来分析一下思路。看着是一个连贯的轮播效果,其实单独看每一个子项,都是完全相同的运动轨迹。
然后是这个动画,其实就是6
个关键帧,逐一去位移和缩放,如下
然后给每个子项不同的“负延迟”,是不是就刚好错开,形成一个完整的轮播效果了呢?
无论CSS
怎么实现,动画原理就这些了,下面来看几个实现方式
二、CSS 动画关键帧
首先简单布局一下,先用一个元素实现动画
<div class="item"></div>
加点修饰
html,body{
font-family: -apple-system, "BlinkMacSystemFont", sans-serif;
margin: 0;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
background: aliceblue;
counter-reset: num;
}
.item{
position: absolute;
display: grid;
place-content: center;
width: 100px;
height: 100px;
border-radius: 8px;
background-color: #3E65FF;
color: #fff;
font-size: 30px;
counter-increment: num;
}
这里的数字是用CSS
计数器生成的,效果如下
根据前面的关键帧,很容易用代码实现,就是
.item{
animation: slide 12s infinite;
}
@keyframes slide {
0%,100% {
transform: translate(0%, 0%) scale(1);
}
16.67% {
transform: translate(120%, -30%) scale(0.9);
}
33.33% {
transform: translate(100%, -70%) scale(0.8);
}
50% {
transform: translate(0, -90%) scale(0.7);
}
66.67% {
transform: translate(-100%, -70%) scale(0.8);
}
83.33% {
transform: translate(-120%, -30%) scale(0.9);
}
}
这里的16.67%
关键帧是将100%
进行6
等分得到的,如下所示
效果如下
虽然有动画了,但是效果有点奇怪,每次改变位置的时候好像没有停顿,显得过渡有些缓慢,只是有减速和加速的过程,这是默认的ease-in-out
的淡入淡出缓冲效果。
那么,如何拉开一定的间隔呢?也就是希望每次运动的快一点,然后停留一会。其实也很简单,在之前的每个关键点前再添加一个相同的关键帧,比如在16.67%
的前面一点6.67%
,由于是相同的,所以这段时间内是没有动画的,也就相当于停留了一会
用代码实现就是
@keyframes slide {
0%,90%,100% {
transform: translate(0%, 0%) scale(1);
}
6.67%,
16.67% {
transform: translate(120%, -30%) scale(0.9);
}
23.33%,
33.33% {
transform: translate(100%, -70%) scale(0.8);
}
40%,
50% {
transform: translate(0, -90%) scale(0.7);
}
56.67%,
66.67% {
transform: translate(-100%, -70%) scale(0.8);
}
73.33%,
83.33% {
transform: translate(-120%, -30%) scale(0.9);
}
}
这样是不是就好多了?
实现了一个,多个子元素也就好办了
<div class="item" style="--i: 0"></div>
<div class="item" style="--i: 1"></div>
<div class="item" style="--i: 2"></div>
<div class="item" style="--i: 3"></div>
<div class="item" style="--i: 4"></div>
<div class="item" style="--i: 5"></div>
我们给每个子元素加个CSS
变量,然后给每个动画加个“负延迟”,让这个动画提前运行到指定位置
.item{
animation: slide 12s calc(-2s * var(--i)) infinite;
}
效果如下
基本就实现这个这个轮播动画,不过层级还有点问题
所以还需要再关键帧里加入层级变化
@keyframes slide {
0%,90%,100% {
z-index: 4;
transform: translate(0%, 0%) scale(1);
}
6.67%,
16.67% {
z-index: 3;
transform: translate(120%, -30%) scale(0.9);
}
23.33%,
33.33% {
z-index: 2;
transform: translate(100%, -70%) scale(0.8);
}
40%,
50% {
z-index: 1;
transform: translate(0, -90%) scale(0.7);
}
56.67%,
66.67% {
z-index: 2;
transform: translate(-100%, -70%) scale(0.8);
}
73.33%,
83.33% {
z-index: 3;
transform: translate(-120%, -30%) scale(0.9);
}
}
这样就完美了
这种实现兼容性最好,只用到了 CSS
动画,兼容市面所有浏览器,可以放心使用
三、CSS @property
其实目前来说,用上面这种方式就足够了,没有任何问题。
不过,上面对于中间停顿的处理方式可能有些繁琐,下面再介绍另一种思路,可能更符合常规。
先思考一下,如果是用 JS
要如何实现?是不是可以直接每隔2
秒改变位移和缩放,然后通过transition
实现过渡效果?
首先,我们还是需要像之前一样,定义一些关键帧,不过不是直接改变位移和缩放,而是改变一些 CSS
变量
@keyframes slide {
0%,100% {
--translate: 0,0;
--scale: 1;
--z: 4;
}
16.67% {
--translate: 120%,-30%;
--scale: 0.9;
--z: 3;
}
33.33% {
--translate: 100%,-70%;
--scale: 0.8;
--z: 2;
}
50% {
--translate: 0%,-90%;
--scale: 0.7;
--z: 1;
}
66.67% {
--translate: -100%,-70%;
--scale: 0.8;
--z: 2;
}
83.33% {
--translate: -120%,-30%;
--scale: 0.9;
--z: 3;
}
}
然后每个子项就需要用transfrom
来应用这些变量了
.item{
transform: translate(var(--translate)) scale(var(--scale));
transition: .5s transform;
animation: slide 12s calc(-2s * var(--i)) infinite;
}
我们来看看效果
好像并没有过渡动画?这是因为animation
覆盖了transition
,所以需要分离开来,我们嵌套一层父级
<div class="item-wrap" style="--i: 0">
<div class="item"></div>
</div>
<div class="item-wrap" style="--i: 1">
<div class="item"></div>
</div>
<div class="item-wrap" style="--i: 2">
<div class="item"></div>
</div>
<div class="item-wrap" style="--i: 3">
<div class="item"></div>
</div>
<div class="item-wrap" style="--i: 4">
<div class="item"></div>
</div>
<div class="item-wrap" style="--i: 5">
<div class="item"></div>
</div>
然后将动画写在父级上
.item-wrap{
animation: slide 12s calc(-2s * var(--i)) infinite;
display: contents;
}
这样就不影响了,效果如下
不过还是有点怪怪的。这是因为animation
的默认效果也是ease-in-out
,所以有这种渐入渐出的效果。我们需要瞬间变化,不需要过渡效果,因为过渡效果可以由transition
完成。
这里我们可以用steps
来实现,steps(1)
表示直接切换,中间没有任何过渡
.item-wrap{
animation: slide 12s calc(-2s * var(--i)) steps(1) infinite;
}
这样效果就和前面基本一致了
你也可以访问以下链接来查看实际效果
虽然里面没有提到CSS @property
,但实际上是依赖这个特性的,因此兼容性稍差,需要Safari 16.4+
,Firefox
目前还不支持
四、CSS 样式查询
抛开兼容性,其实还有一种方式可以实现,而且更容易理解,也更符合JS
的思路。
我们要做的动画很简单,只需要改变一个 CSS
变量就行,如下
@property --index {
syntax: "<number>";
initial-value: 0;
inherits: false;
}
@keyframes slide {
0% {
--index: 0;
}
100% {
--index: 6;
}
}
通过@property
可以让--index
变量从0→6
一次变化
关于这个技巧,之前在多篇文章中都有提到
实现如下
.item-wrap{
display: contents;
animation: slide 12s calc(-2s * var(--i)) steps(6) infinite;
}
这样就实现了一个--index
不断变化的动画。
当然仅仅只是这样还不够,我们需要根据这个变量来匹配具体的样式,这就要用到CSS
样式查询了,具体实现如下
@container style(--index: 0) {
.item {
transform: translate(0, 0) scale(1);
z-index: 4;
}
}
@container style(--index: 1) {
.item {
transform: translate(120%, -30%) scale(0.9);
z-index: 3;
}
}
@container style(--index: 2) {
.item {
transform: translate(100%, -70%) scale(0.8);
z-index: 2;
}
}
@container style(--index: 3) {
.item {
transform: translate(0, -90%) scale(0.7);
z-index: 1;
}
}
@container style(--index: 4) {
.item {
transform: translate(-100%, -70%) scale(0.8);
z-index: 2;
}
}
@container style(--index: 5) {
.item {
transform: translate(-120%, -30%) scale(0.9);
z-index: 3;
}
}
这段应该很好理解,比如@container style(--index: 5)
表示,当查询到--index
为5
的时候,下面的样式就生效了,非常像通过 JS
来改变类名一样,这种方式也能实现类似的效果
你也可以访问以下链接来查看实际效果
由于要用到样式查询,所以兼容性更差一点,需要Chrome 111+
,酌情使用
五、总结一下
以上就是本文的全部内容了,共介绍了3
种不同的实现思路,兼容性从高到低,大家可以自行选择。
对了,还有一点,有时候我们需要鼠标hover
时暂停动画,这个就体现出CSS
的优势了,直接用:hover
实现,类似这样
.wrap:hover .item{
animation-play-state: paused;
}
下面总结一下实现要点
- 整体看着是一个连贯的轮播效果,其实单独看每一个子项,都是完全相同的运动轨迹
- 给每个子项不同的“负延迟”,就能形成一个完整的轮播效果
- 动画本质上是
6
个关键帧,只是位移和缩放的变化 - 直接使用关键帧虽然有动画,但是效果有点奇怪,每次改变位置的时候好像没有停顿,显得过渡有些缓慢
- 可以手动在每个关键点前再添加一个相同的关键帧,手动停顿一下
- 给每个子元素加个
CSS
变量,通过这个变量可以计算每个动画的“负延迟”,让这个动画提前运行到指定 - 我们还定义一些关键帧,不过不是直接改变位移和缩放,而是改变一些
CSS
变量 - 这样可以实现每隔
2
秒改变位移和缩放,然后通过transition
实现过渡效果 - 这种思路依赖
CSS @property
,兼容性稍差,需要Safari 16.4+
,Firefox
目前还不支持 - 还可以通过样式查询,自动匹配每种变量的位移和缩放,更符合常规思路,需要
Chrome 111+
当然目前还是最推荐第一种方式的,兼容性最好,可以放心使用,其他两种方式也可以了解思路,以后肯定用得上。
关注我,学习更多有趣的前端交互小技巧。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。