CSS动画--老虎机数字滚动效果

3,676 阅读2分钟

动画效果

  • 之前在项目中看到过类似老虎机的数字滚动效果
  • 突发奇想,通过纯CSS实现一个类似老虎机一样的数字滚动动画
  • 效果图如下:

mnggiflab-download (2).gif

原理分析

  • 这玩意就和垂直方向的轮播图差不多
  • 只是把轮播的图换成数字

image-20221024183107917.png

为什么头尾都有0?

  • 其实循环播放的轮播图也是如此,为了实现无缝滚动,通常会在最后加上第一张的复制版
  • 再加个 overflow: hidden,将溢出的数字隐藏,如下效果

1666624924415.png

动画定位

  • 先取其中一个,假如在动画停止时,想让数字定格在 8 这个数字,如下效果:

mnggiflab-download.gif

注意:

  • 数字在滚动时,是带有模糊效果的
  • 结束之后,模糊效果已经消失
  • 可以发现数字并不是恰巧滚动到 8 这个位置停住,而是只要动画结束就直接定位到 8
  • 由于滚动速度快,人眼也看不出来,其实也就是障眼法
  • CSS 很多特效都靠类似于障眼法一样实现的,如无限滚动的轮播图

定位是如何做到的?

  • 通过 Y 轴移动,动画结束时向上移动到对应位置
 transform: translateY(calc(8 * -9.09%));
 /* 8是示例,不可写死 */

1666627982681.png

-9.09%是如何算出来的?

  • 总共 11 个数字,11个数字总高度 / (数字高度 + 行高)1/11 ≈ 0.0909

动画拆解

  • ①数字无限滚动动画,滚动的时间通过传参决定

mnggiflab-download (1).gif

 @keyframes move {
   0% {
     transform: translateY(-90%);
     filter: blur(2px);
   }
   100% {
     transform: translateY(1%);
     filter: blur(2px);
   }
 }

  • ②数字抖动,滚动动画结束后,数字上下抖动

bounce-num.gif

 @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"
 />

1666630882954.png

组件封装的思路

  • 用到了CSS变量+calc函数来控制滚动时长和停留位置
  • 利用calc函数保证宽高等比缩放
  • 需要调整大小的话,则传入宽度控制

参考: juejin.cn/post/698645…