动画效果
- 之前在项目中看到过类似老虎机的数字滚动效果
- 突发奇想,通过纯CSS实现一个类似老虎机一样的数字滚动动画
- 效果图如下:
原理分析
- 这玩意就和垂直方向的轮播图差不多
- 只是把轮播的图换成数字
为什么头尾都有0?
- 其实循环播放的轮播图也是如此,为了实现无缝滚动,通常会在最后加上第一张的复制版
- 再加个
overflow: hidden
,将溢出的数字隐藏,如下效果
动画定位
- 先取其中一个,假如在动画停止时,想让数字定格在
8
这个数字,如下效果:
注意:
- 数字在滚动时,是带有模糊效果的
- 结束之后,模糊效果已经消失
- 可以发现数字并不是恰巧滚动到
8
这个位置停住,而是只要动画结束就直接定位到8
- 由于滚动速度快,人眼也看不出来,其实也就是障眼法
CSS
很多特效都靠类似于障眼法一样实现的,如无限滚动的轮播图
定位是如何做到的?
- 通过
Y
轴移动,动画结束时向上移动到对应位置
transform: translateY(calc(8 * -9.09%));
/* 8是示例,不可写死 */
-9.09%是如何算出来的?
- 总共
11
个数字,11个数字总高度 / (数字高度 + 行高) ,1/11 ≈ 0.0909
动画拆解
- ①数字无限滚动动画,滚动的时间通过传参决定
@keyframes move {
0% {
transform: translateY(-90%);
filter: blur(2px);
}
100% {
transform: translateY(1%);
filter: blur(2px);
}
}
- ②数字抖动,滚动动画结束后,数字上下抖动
@keyframes num-bounce {
0% {
transform: translateY(calc(8 * -9.09% - 7%));
filter: none; /* 最后取消模糊效果 */
}
25% {
transform: translateY(calc(8 * -9.09% + 3%));
}
50% {
transform: translateY(calc(8 * -9.09% - 1%));
}
70% {
transform: translateY(calc(8 * -9.09% + 0.6%));
}
85% {
transform: translateY(calc(8 * -9.09% - 0.3%));
}
100% {
transform: translateY(calc(8 * -9.09%));
filter: none; /* 最后取消模糊效果 */
}
}
- 边框抖动,数字抖动的同时,边框也跟着抖动
@keyframes border-bounce {
25% {
transform: translateY(7%);
}
50% {
transform: translateY(-3%);
}
70% {
transform: translateY(4%);
}
85% {
transform: translateY(-1%);
}
100% {
transform: translateY(0);
}
}
动画代码
- 封装成组件
<ScrollNum />
<template>
<div
class="scroll-box border-animate"
:style="scrollStyle"
@animationend="showAnimate = false"
>
<ul ref="num" class="scroll-num" :class="{ animate: showAnimate }">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>0</li>
</ul>
</div>
</template>
<script>
export default {
name: "ScrollNum",
props: {
// 定位的数字
num: {
type: Number,
default: 0,
validator: (n) => n < 10 && n >= 0 && Number.isInteger(n),
},
// 每个数字延迟停止
delay: { type: Number, default: 1 },
// 自定义尺寸
size: { type: String, default: "small" },
},
data: () => ({
showAnimate: true, // 控制无限滚动结束
}),
computed: {
scrollStyle() {
const sizeMap = {
small: "20px",
medium: "36px",
large: "56px",
}[this.size];
return {
"--num": this.num, // CSS变量,提供到style使用
"--delay": this.delay,
"--width": sizeMap,
};
},
},
};
</script>
<style scoped>
.scroll-box {
width: var(--width, 20px);
height: calc(var(--width, 20px) * 1.8);
color: #fff;
font-size: calc(var(--width, 20px) * 1.1);
line-height: calc(var(--width, 20px) * 1.8);
text-align: center;
border: 1px solid #fff;
border-radius: 5px;
margin-right: 30px;
overflow: hidden;
flex-shrink: 0; /* 保证不被压缩 */
}
.animate {
animation: move 1s linear infinite,
num-bounce 1s calc(var(--delay) * 1s) forwards;
/* forwards:停在动画的最后一帧 */
}
.border-animate {
animation: border-bounce 1s calc(var(--delay) * 1s) forwards;
}
.scroll-num {
padding: 0;
margin: 0;
list-style: none;
transform: translateY(calc(var(--num) * -9.09%));
}
</style>
- 使用方式
- 传入一个数字数组,如
['8','8','8','8']
- num: 当前需要停留的数字
- delay: 每个数字延迟的时间
- size: 自定义尺寸
<ScrollNum
v-for="(num, index) in numList"
:key="index"
:num="num"
:delay="index + 1"
width="40px"
/>
组件封装的思路
- 用到了CSS变量+calc函数来控制滚动时长和停留位置
- 利用calc函数保证宽高等比缩放
- 需要调整大小的话,则传入宽度控制