前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果

作者 : admin 本文共21068个字,预计阅读时间需要53分钟 发布时间: 2024-06-17 共1人阅读

其实没有做多复杂的效果,连 canvas 都没用上,都是一些简单的平面变换,不过一段看似复杂的动画往往都是几个简单的变换拼接而成,所以我们逐步拆解,很简单的就能得到一个扭蛋机十连抽效果。

语言环境

我这边使用的是 tailwindcss 和 ts,在 uniapp  + vue3 的情况下写的小程序扭蛋机例子,不同框架下的同学可能要转换一下。

为了方便同学们学习,里面的素材都是远程图库的图片,可以直接取用。

先看效果

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图

1.背景

先搭一个背景界面和主框体



  
    
    
  





.page {
  width: 100vw;
  min-height: 100vh;
  overflow: hidden;
  background-color: #ff0027;
}

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(1)

就一个主背景,我在小程序上开发,所以单位用的 rpx,如果有用 px 做单位的,一般换算方法是 1px = 2rpx 。

2.弹幕动画:随机高度的左右平移

弹幕动画的本质是五张图片随机在界面上做平移处理,超出小程序界面则隐藏。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(2)

1.弹幕template:

        设定一个弹幕显示区域后,为其添加 overflow: hidden 属性,这样弹幕在进出容器边缘时隐藏。

        设置弹幕图片为  mode=”heightFix” ,即等高情况下自适应宽度(实际开发情况根据你的素材做调整),由于我这里的素材故意设置的大小不同,因此在以同样的 transform 样式做平移的时候,速度上会有参差感。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(3)

2.弹幕scss

        样式中选择平移变换,这里解释一下为什么用 translate3d 而不是 translateX,主要原因是为了利用硬件加速,提高动画的性能和流畅性。尤其是iOS上浏览器在处理3D变换时通常会触发硬件加速,并且最重要的是非 translate3d 效果在ios的小程序上经常被吞(可恶的ios)

        然后动画中采用用百分比平移变换的原因是每个弹幕的长度不一样,导致的平移的速度也会有差别,能起到更加随机的视觉效果

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(4)

3.弹幕ts

        有两个属性需要用代码做随机设置,一个是弹幕的高度,一个是弹幕出场的时间,即动画延迟时间。每个弹幕对象包含:top(弹幕的顶部位置)、startAnimation(控制动画是否开始)、src(弹幕内容或来源)

        这里弹幕我没有设置随机重新刷新,因为CSS动画已经是循环播放的,如果想要更加随机一点,后面可以再优化。目前的效果是每次进入页面,随机一种弹幕位置效果。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(5)

4.弹幕模块完整代码(背景 + 弹幕)



  
    
    

    
    
      
      
    
  





.page {
  width: 100vw;
  min-height: 100vh;
  overflow: hidden;
  background-color: #ff0027;
}

/* 弹幕平移动画 */
.barrage-animation {
  animation-name: moveLeft; /* 指定动画名称 */
  animation-duration: 10s; /* 指定动画时长 */
  animation-play-state: running; /* 控制动画播放状态 */
  animation-timing-function: linear; /* 指定动画速度曲线 */
  animation-iteration-count: infinite; /* 指定动画循环次数 */
}

/* 这里解释一下为什么用 translate3d 而不是 translateX,主要原因是为了利用硬件加速,提高动画的性能和流畅性。尤其是iOS上浏览器在处理3D变换时通常会触发硬件加速,并且最重要的是非 translate3d 效果在ios的小程序上经常被吞(可恶的ios) */
@keyframes moveLeft {
  /* 这里用百分比平移变换的原因是每个弹幕的长度不一样,导致的平移的速度也会有差别,能起到更加随机的视觉效果 */
  0% { transform: translate3d(100%, 0, 0) }
  100% { transform: translate3d(-500%, 0, 0) }
}

3.中奖记录:渐隐渐现的上下平移

        中奖记录的实现是通过左侧元素的上下平移再搭配透明度来实现,这里我是通过动态计算 top 的位置以及主动设置中间三项的透明度来实现。其实如果只是做效果,可以只用纯静态css,要简单很多,但是我这里要考虑到实际对接接口,而接口数据的长度是不可控的,因此通过脚本来灵活计算。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(6)

1.中奖记录template与scss:

这里没有太多介绍的,核心上面已经说了,通过控制 top 和 opacity 属性来控制动画,记得加上 transition: all 0.4s ease; 否则是没有过渡效果的。ellipsis 单行省略三件套是我喜欢用的样式,在设定宽度的情况下,它能让内部超出范围的文字截取并用省略号代替。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(7)

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(8)

2.中奖记录ts:

通过 showTop 来设定最上面那条显示的中奖记录的高度,这个值越大,中奖记录的显示范围就越靠下;showCount 用来设置显示的条目,我这里是控制其显示三条;intervalId 是轮询任务的id,在这里记录下来是为了离开页面的时候要将其销毁,否则会一直占用内存。

变换方式是,追加一个定时任务,每隔三秒将 recordList 里面每一项的 top 值减去60; 并且判断 在 top >= showTop && top <= showTop + (60 * showCount) 的范围内 设 show 为 true。最下面还有个 clearInterval(intervalId.value) 方法由于图片尺寸问题没有截到,大家直接看后面的源码。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(9)

3.中奖记录模块完整代码(背景 + 弹幕 + 中奖记录)



  
    
    

    
    
      
      
    

    
     1624 ? 1624 : item.top) + 'rpx', 'opacity': item.show?1:0, 'z-index': item.show ? 1000: -1 }"
    >
      {{ item.phone + '获得' + item.prizeName }}
    
  





.page {
  width: 100vw;
  min-height: 100vh;
  overflow: hidden;
  background-color: #ff0027;
}

/* 弹幕平移动画 */
.barrage-animation {
  animation-name: moveLeft; /* 指定动画名称 */
  animation-duration: 10s; /* 指定动画时长 */
  animation-play-state: running; /* 控制动画播放状态 */
  animation-timing-function: linear; /* 指定动画速度曲线 */
  animation-iteration-count: infinite; /* 指定动画循环次数 */
}

/* 这里解释一下为什么用 translate3d 而不是 translateX,主要原因是为了利用硬件加速,提高动画的性能和流畅性。尤其是iOS上浏览器在处理3D变换时通常会触发硬件加速,并且最重要的是非 translate3d 效果在ios的小程序上经常被吞(可恶的ios) */
@keyframes moveLeft {
  /* 这里用百分比平移变换的原因是每个弹幕的长度不一样,导致的平移的速度也会有差别,能起到更加随机的视觉效果 */
  0% { transform: translate3d(100%, 0, 0) }
  100% { transform: translate3d(-500%, 0, 0) }
}

/* 中奖记录轮播动画 */
.left-bar{
  position: absolute;
  left: 106rpx;
  z-index: 100;
  max-width: 312rpx;
  padding: 8rpx 16rpx;
  color: #fff;
  font-size: 24rpx;
  background: rgb(0 0 0 / 0.3);
  border-radius: 50rpx;
  transition: all 0.4s ease;
}
.ellipsis {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

4.小球动画:基于定位的平移动画

扭蛋动画就是直接设置小球的 top 和 left 坐标。

重要:这里解释一下为什么小球变换要用定位而不是更省性能的 transform: 因为部分机型对transform支持性不好,会被过滤掉(比如ios上transform2d不生效,1加ace2对transform单项变换不生效),因此这种核心动画我选用定位来执行。

其实如果大家对机型的适配性要求不高,我是十分推荐使用 transform3d 来执行大量的变换的,会更加节省性能,如果实在无法避免使用定位来执行大量的变换,尽量将元素脱离文档流,比如设置为绝对定位。

这里由于转了gif图被抽帧所以看起来动画不够连贯,但是其实只要小球数量不是特别多,效果还是很丝滑的。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(10)

1.小球及容器 template

将扭蛋范围固定在容器 lottery-box-content 中并设置超出隐藏,这样就算小球的定位设置失误,使其超出了框体,也由于被隐藏所以没有违和感。

由于小球素材只有四种球,因此通过序列号 i%4 取余数来分配小球的外观,由于本篇文章重点都放在动画和效果的实现上,所以这里积分数据都是写死固定的,真实情况可以根据每次扭蛋来更新剩余分数。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(11)

2.小球及容器 scss

样式代码有很多,一条条来看。

首先是扭蛋机的框体和小球的样式,小球将定位单独抽出,方便后续较为直观的使用动画控制移动。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(12)

然后是球的掉落动画,在入场和扭蛋结束后执行,就是做一个竖直方向的来回震动,这个动画不是很重要,即便被ios屏蔽掉也影响不大,因此这里选用了 translateY 来执行。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(13)

最后是最为核心的小球移动动画,我这里手动测量出了小球在容器中的移动范围是 水平0-464 像素之间 ;  垂直方向 0-536 像素之间。

这也是我选择使用定位而不是transform来执行小球动画的原因,当我知道了扭蛋机边界的位置的时候,我可以很自然的模拟出更加真实的小球移动的路径(当x或y至少一个值到达了边界,就可以转换方向),而不用担心出现小球越界甚至虚空换向的问题了。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(14)

3.小球及容器 ts

ts中用来初始化小球以及衔接上面的小球动画,其实就是通过 animateClass 变量来控制 run_? 样式是否生效。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(15)

4.小球模块完整代码(背景 + 弹幕 + 中奖记录 + 小球)


1624 ? 1624 : item.top) + 'rpx', 'opacity': item.show?1:0, 'z-index': item.show ? 1000: -1 }"
>
{{ item.phone + '获得' + item.prizeName }}

单抽
10
十连
90
积分:10000
.page {
width: 100vw;
min-height: 100vh;
overflow: hidden;
background-color: #ff0027;
}
/* 弹幕平移动画 */
.barrage-animation {
animation-name: moveLeft; /* 指定动画名称 */
animation-duration: 10s; /* 指定动画时长 */
animation-play-state: running; /* 控制动画播放状态 */
animation-timing-function: linear; /* 指定动画速度曲线 */
animation-iteration-count: infinite; /* 指定动画循环次数 */
}
/* 这里解释一下为什么用 translate3d 而不是 translateX,主要原因是为了利用硬件加速,提高动画的性能和流畅性。尤其是iOS上浏览器在处理3D变换时通常会触发硬件加速,并且最重要的是非 translate3d 效果在ios的小程序上经常被吞(可恶的ios) */
@keyframes moveLeft {
/* 这里用百分比平移变换的原因是每个弹幕的长度不一样,导致的平移的速度也会有差别,能起到更加随机的视觉效果 */
0% { transform: translate3d(100%, 0, 0) }
100% { transform: translate3d(-500%, 0, 0) }
}
/* 中奖记录轮播动画 */
.left-bar{
position: absolute;
left: 106rpx;
z-index: 100;
max-width: 312rpx;
padding: 8rpx 16rpx;
color: #fff;
font-size: 24rpx;
background: rgb(0 0 0 / 0.3);
border-radius: 50rpx;
transition: all 0.4s ease;
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 以下都是扭蛋机相关的动画 */
.lottery-box .lottery-box-content{
position: absolute;
top: 390rpx;
left: 92rpx;
z-index: 99;
width: 566rpx;
height: 680rpx;
overflow: hidden;
.qiu {
position: absolute;
display: block;
width: 100rpx;
height: 100rpx;
}
/* 各个球的定位 */
.qiu_0 { top: 483rpx; left: 430rpx; }
.qiu_1 { top: 384rpx; left: 32rpx; }
.qiu_2 { top: 482rpx; left: 23rpx; }
.qiu_3 { top: 580rpx; left: 10rpx; }
.qiu_4 { top: 438rpx; left: 115rpx; }
.qiu_5 { top: 500rpx; left: 186rpx; }
.qiu_6 { top: 542rpx; left: 100rpx; }
.qiu_7 { top: 458rpx; left: 278rpx; }
.qiu_8 { top: 580rpx; left: 248rpx; }
.qiu_9 { top: 577rpx; left: 462rpx; }
.qiu_10 { top: 537rpx; left: 340rpx; }
.qiu_11 { top: 480rpx; left: 333rpx; }
.diaol_0{animation:dropOut 1s linear 1.4s backwards;}
.diaol_0::after{animation-delay:1.3s;}
.diaol_1{animation:dropOut 1s linear 0.9s backwards;}
.diaol_1::after{animation-delay:0.8s;}
.diaol_2{animation:dropOut 1s linear 0.6s backwards;}
.diaol_2::after{animation-delay:0.5s;}
.diaol_3{animation:dropOut 1s linear  backwards;}
.diaol_4{animation:dropOut 1s linear 1.1s backwards;}
.diaol_4::after{animation-delay:1s;}
.diaol_5{animation:dropOut 1s linear 0.8s backwards;}
.diaol_5::after{animation-delay:0.7s;}
.diaol_6{animation:dropOut 1s linear 0.4s backwards;}
.diaol_6::after{animation-delay:0.3s;}
.diaol_7{animation:dropOut 1s linear 0.9s backwards;}
.diaol_7::after{animation-delay:0.8s;}
.diaol_8{animation:dropOut 1s linear 0.6s backwards;}
.diaol_8::after{animation-delay:0.5s;}
.diaol_9{animation:dropOut 1s linear 1.1s backwards;}
.diaol_9::after{animation-delay:1s;}
.diaol_10{animation:dropOut 1s linear 0.2s backwards;}
.diaol_11{animation:dropOut 1s linear 1.4s backwards;}
.diaol_11::after{animation-delay:1.3s;}
@keyframes dropOut {
0% { transform: translateY(-200%); opacity: 0; }
5% { transform: translateY(-200%); }
15% { transform: translateY(0); }
30% { transform: translateY(-100%); }
40% { transform: translateY(0%); }
50% { transform: translateY(-60%); }
70% { transform: translateY(0%); }
80% { transform: translateY(-30%); }
90% { transform: translateY(0%); }
95% { transform: translateY(-14%); }
97% { transform: translateY(0%); }
99% { transform: translateY(-6%); }
100% { transform: translateY(0); opacity: 1; }
}
.run_0 {animation:around0  1.5s linear  infinite;}
.run_1 {animation:around1  1.5s linear  infinite;}
.run_2 {animation:around2  1.5s linear  infinite;}
.run_3 {animation:around3  1.5s linear  infinite;}
.run_4 {animation:around4  1.5s linear  infinite;}
.run_5 {animation:around5  1.5s linear  infinite;}
.run_6 {animation:around6  1.5s linear  infinite;}
.run_7 {animation:around7  1.5s linear  infinite;}
.run_8 {animation:around8  1.5s linear  infinite;}
.run_9 {animation:around9  1.5s linear  infinite;}
.run_10{animation:around10 1.5s linear  infinite;}
.run_11{animation:around11 1.5s linear  infinite;}
/* 移动范围 left 0-464 ;  top 0-536 */
/* 这里解释一下为什么小球变换要用定位而不是更省性能transform: 因为部分机型对transform支持性不好,会被过滤掉(比如ios上transform2d不生效,1加ace2对transform单项变换不生效)因此这种关键动画使用定位来执行 */
@keyframes around0 {
0%    { top: 483rpx; left: 430rpx; }
20%   {   top: 536rpx; left: 0rpx; }
40%   { top: 0rpx; left: 464rpx;   }
60%   {   top: 200rpx; left: 0rpx; }
80%   { top: 3rpx; left: 303rpx;   }
100%  { top: 483rpx; left: 430rpx; }
}
@keyframes around1 {
0%    {  top: 384rpx; left: 32rpx; }
16%   { top: 200rpx; left: 450rpx; }
32%   {   top: 24rpx; left: 8rpx;  }
48%   { top: 500rpx; left: 106rpx; }
64%   {   top: 6rpx; left: 0rpx;   }
82%   { top: 180rpx; left: 464rpx; }
100%  {  top: 384rpx; left: 32rpx; }
}
@keyframes around2 {
0%    {  top: 482rpx; left: 23rpx; }
20%   { top: 64rpx; left: 448rpx;   }
40%   {  top: 520rpx; left: 16rpx;  }
60%   { top: 208rpx; left: 432rpx;  }
80%   {  top: 8rpx; left: 80rpx;    }
100%  {  top: 482rpx; left: 23rpx;  }
}
@keyframes around3 {
0%    {  top: 580rpx; left: 10rpx; }
20%   { top: 100rpx; left: 300rpx;  }
40%   {  top: 20rpx; left: 10rpx;   }
60%   { top: 400rpx; left: 460rpx;  }
80%   { top: 68rpx; left: 220rpx;   }
100%  {  top: 580rpx; left: 10rpx;  }
}
@keyframes around4 {
0%    { top: 438rpx; left: 115rpx; }
16%   { top: 10rpx; left: 300rpx;  }
32%   {  top: 530rpx; left: 30rpx;  }
48%   { top: 200rpx; left: 450rpx;  }
64%   {  top: 300rpx; left: 20rpx;  }
82%   { top: 560rpx; left: 450rpx;  }
100%  { top: 438rpx; left: 115rpx;  }
}
@keyframes around5 {
0%    { top: 500rpx; left: 186rpx; }
20%   {  top: 200rpx; left: 50rpx;  }
40%   { top: 350rpx; left: 400rpx;  }
60%   { top: 530rpx; left: 100rpx;  }
80%   { top: 100rpx; left: 380rpx;  }
100%  { top: 500rpx; left: 186rpx;  }
}
@keyframes around6 {
0%    {  top: 542rpx; left: 100rpx; }
15%   {  top: 300rpx; left: 300rpx;  }
30%   {  top: 100rpx; left: 100rpx;  }
45%   {  top: 200rpx; left: 400rpx;  }
60%   {  top: 400rpx; left: 200rpx;  }
75%   {  top: 100rpx; left: 450rpx;  }
100%  {  top: 542rpx; left: 100rpx;  }
}
@keyframes around7 {
0%    {  top: 458rpx; left: 278rpx; }
15%   {   top: 200rpx; left: 50rpx;  }
35%   {  top: 150rpx; left: 450rpx;  }
55%   {   top: 520rpx; left: 50rpx;  }
75%   {  top: 250rpx; left: 450rpx;  }
90%   {   top: 530rpx; left: 20rpx;  }
100%  {  top: 458rpx; left: 278rpx;  }
}
@keyframes around8 {
0%    {  top: 580rpx; left: 248rpx;  }
20%   {   top: 350rpx; left: 50rpx;  }
40%   {  top: 10rpx; left: 460rpx;   }
60%   {  top: 536rpx; left: 460rpx;  }
80%   {  top: 20rpx; left: 380rpx;   }
100%  {  top: 580rpx; left: 248rpx;  }
}
@keyframes around9 {
0%    {  top: 577rpx; left: 462rpx; }
12.5% {  top: 400rpx; left: 300rpx;  }
25%   {  top: 450rpx; left: 500rpx;  }
37.5% {  top: 350rpx; left: 200rpx;  }
50%   {  top: 250rpx; left: 450rpx;  }
62.5% {  top: 400rpx; left: 150rpx;  }
75%   {  top: 150rpx; left: 350rpx;  }
87.5% {  top: 500rpx; left: 250rpx;  }
100%  {  top: 577rpx; left: 462rpx;  }
}
@keyframes around10 {
0%    {  top: 537rpx; left: 340rpx;  }
15%   {  top: 400rpx; left: 150rpx;  }
30%   {  top: 350rpx; left: 450rpx;  }
50%   {   top: 50rpx; left: 50rpx;   }
70%   {  top: 450rpx; left: 400rpx;  }
85%   {  top: 550rpx; left: 120rpx;  }
100%  {  top: 537rpx; left: 340rpx;  }
}
@keyframes around11 {
0%   { top: 480rpx; left: 333rpx; }
16%  { top: 350rpx; left: 464rpx; }
33%  { top: 400rpx; left: 200rpx; }
50%  { top: 200rpx; left: 400rpx; }
66%  { top: 300rpx; left: 100rpx; }
83%  { top: 100rpx; left: 300rpx; }
100% { top: 480rpx; left: 333rpx; }
}
.ball_0{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball0.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_1{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball1.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_2{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball2.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_3{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball3.png');
background-repeat: no-repeat;
background-size: 100%;
}
}

5.标题箭头,基于关键帧的叠三角指向效果

这里通过控制两个三角形透明度动画的关键帧的先后顺序,达到一种箭头由外指向内的视觉效果。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(16)

这里动画很快,转成gif图看动画也不明显,就不贴图了,可以根据视频里看出这里标题的动画效果。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(17)

这一块不是很重要,属于锦上添花的功能,源码就不单独贴出了,放在后面一起给出吧。

6.奖品动画,渐变背景添加扫光

最重要的地方来了,你以为的扭蛋机核心效果:小球乱窜效果;实际的扭蛋机核心效果:奖品弹窗效果。

扭蛋中的小球平移效果其实并不是很重要,因为用户视觉上只会停留很短的时间(一秒左右)就会被奖品弹窗给覆盖,因此奖品弹窗效果才是最重要的模块(用户视觉停留最长)。

这里奖品弹窗使用五层效果叠加,即 遮罩+底图+奖品图+蒙版+扫光层 的效果,因此要注意 z-index 的层级设置。

单抽和十连抽效果不同,先分开讲。

单抽效果比较简单,只需要添加一层扫光效果即可。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(18)

扫光的效果是,先绘制一条斜向的渐变div,接着以纵向或横向的方式平移即可。通过渐变让一道白光一闪而过,一开始使用的斜向位移,后来发觉只要背景是斜的,哪怕只做一个维度的平移而形成的视觉效果也像斜向扫描,因此去掉水平渐变。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(19)

接下来是十连抽的效果图,有了单抽效果图做铺垫,十连抽就很好理解了,只不过增加了十个扭蛋之间的依次展现的效果。

十连抽的核心在于,灵活设置不同的 animation-delay 属性,让多个商品之间的动画有错落感。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(20)

十连抽scss:

十连抽中有个翻转动画,如果是在 h5 端就很容易实现,直接使用 backface-visibility: hidden 让div背部的元素隐藏,可惜这里是小程序,backface-visibility: hidden 不生效,只能通过 opacity 来让翻转到背面的元素隐藏。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(21)

在ts中额外设置十连抽商品弹窗的位置,这里单独拿出来的 delay 属性是专门控制扫光动画的,渐现动画和翻转动画都是根据商品下标来按顺序来的,但是扫光动画是从左下角扫到右上角,所以独立设置一下delay属性。

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图(22)效果如下:

前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果插图

最后的扫光动画一定要加上,它是立体感的灵魂。

7.案例源码

注释写的很详细,可能有些同学没有在 tailwindcss 环境下,不过问题不大,那些模板中的样式都能望文知义,很好理解。


1624 ? 1624 : item.top) + 'rpx', 'opacity': item.show?1:0, 'z-index': item.show ? 1000: -1 }"
>
{{ item.phone + '获得' + item.prizeName }}

单抽
10
十连
90
积分:10000
奖品展示
锐星优惠券
¥5.5
—— 锐星优惠券 ——
锐星优惠券

{{ drawType===1 ? '再抽一次':'再抽十次' }}
.page {
width: 100vw;
min-height: 100vh;
overflow: hidden;
background-color: #ff0027;
}
/* 弹幕平移动画 */
.barrage-animation {
animation-name: moveLeft; /* 指定动画名称 */
animation-duration: 10s; /* 指定动画时长 */
animation-play-state: running; /* 控制动画播放状态 */
animation-timing-function: linear; /* 指定动画速度曲线 */
animation-iteration-count: infinite; /* 指定动画循环次数 */
}
/* 这里解释一下为什么用 translate3d 而不是 translateX,主要原因是为了利用硬件加速,提高动画的性能和流畅性。尤其是iOS上浏览器在处理3D变换时通常会触发硬件加速,并且最重要的是非 translate3d 效果在ios的小程序上经常被吞(可恶的ios) */
@keyframes moveLeft {
/* 这里用百分比平移变换的原因是每个弹幕的长度不一样,导致的平移的速度也会有差别,能起到更加随机的视觉效果 */
0% { transform: translate3d(100%, 0, 0) }
100% { transform: translate3d(-500%, 0, 0) }
}
/* 中奖记录轮播动画 */
.left-bar{
position: absolute;
left: 106rpx;
z-index: 100;
max-width: 312rpx;
padding: 8rpx 16rpx;
color: #fff;
font-size: 24rpx;
background: rgb(0 0 0 / 0.3);
border-radius: 50rpx;
transition: all 0.4s ease;
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 缓入动画,百搭 */
.loaded{
opacity: 0;
animation: show .5s ease forwards;
@keyframes show{
from{ opacity: 0; }
to{ opacity: 1; }
}
}
/**
* 三角标题动画
* 动画核心是让外侧的三角形先开始亮,极短的时间内内侧三角形再亮,造成一种箭头指向的视觉效果
*/
.triangle {
position: relative;
width: 0;
height: 0;
border-color: transparent transparent #fff;
border-style: solid;
border-width: 0 16rpx 28rpx;
&.inside  {
animation: Inside 2s infinite;
@keyframes Inside {
0% { opacity: 0.7; }
27% { opacity: 0.7; }
57% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0.7; }
}
}
&.outside {
animation: Outside 2s infinite;
@keyframes Outside {
0% { opacity: 0.7; }
20% { opacity: 0.7; }
50% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0.7; }
}
}
}
/**
* 商品扫描动画
* 通过渐变让一道白光一闪而过
* 一开始使用的斜向位移,后来发觉只要背景是斜的,哪怕只做一个维度的平移而形成的视觉效果也像斜向扫描,因此去掉水平渐变
*/
.scan-overlay {
position: absolute;
top: -400rpx;
left: -100rpx;
width: 550rpx;
height: 550rpx;
background: linear-gradient(
to right top,
transparent 0%,
transparent 40%, /* 开始渐变前的透明区域 */
rgb(255 255 255 / 0.1) 50%, /* 渐变开始,较低的透明度 */
rgb(255 255 255 / 0.6) 60%, /* 渐变中间,较高的透明度 */
rgb(255 255 255 / 0.1) 70%, /* 渐变结束,较低的透明度 */
transparent 80%, /* 结束渐变后的透明区域 */
transparent 100%
);
/* 设置足够长的时间让效果看起来是轮次执行的感觉 */
animation: scan 6s 0.6s linear infinite;
/* 下面这个关键帧的时间不要改广了,不然像是乱窜 */
@keyframes scan{
0%{ top: 600rpx; }
10%{ top: -400rpx; }
100%{ top: -400rpx; }
}
}
// 给商品名称一个扫描同步的闪动动画
.flicker{
opacity: 0.8;
// 注意如果要改这里的时间,上面扫描的时间最好也要同步更改
animation: flicker 6s 1s linear infinite;
@keyframes flicker{
0%{ opacity: 0.8; }
14%{ opacity: 0.8; }
20%{ opacity: 1; }
36%{ opacity: 1; }
42%{ opacity: 0.8; }
100%{ opacity: 0.8 }
}
}
// 十连扫光动画
.scan-overlay-ten {
position: absolute;
top: 200rpx;
left: -70rpx;
width: 300rpx;
height: 330rpx;
background: linear-gradient(
to right top,
transparent 0%,
transparent 40%, /* 开始渐变前的透明区域 */
rgb(255 255 255 / 0.1) 50%, /* 渐变开始,较低的透明度 */
rgb(255 255 255 / 0.6) 60%, /* 渐变中间,较高的透明度 */
rgb(255 255 255 / 0.1) 70%, /* 渐变结束,较低的透明度 */
transparent 80%, /* 结束渐变后的透明区域 */
transparent 100%
);
/* 设置足够长的时间让效果看起来是轮次执行的感觉 */
animation: scan-ten 6s linear infinite;
/* 下面这个关键帧的时间不要改广了,不然像是乱窜 */
@keyframes scan-ten{
0%{ top: 200rpx; }
15%{ top: -300rpx; }
100%{ top: -300rpx; }
}
}
/* 十连扭蛋的奖品 */
.toy-box{
left: 22rpx;
top: 4rpx;
width: 136rpx;
height: 136rpx;
border-radius: 200rpx;
overflow: hidden;
}
.toy{
position: relative;
perspective: 1000px;
transform: rotateY(180deg);
animation: flipAnimation 1s 1s forwards; /* 动画名称,持续时间,延迟时间,填充模式 */
.front, .back {
position: absolute;
width: 100%;
height: 100%;
}
.front image, .back image {
width: 100%;
height: 100%;
}
/* 小程序中大部分机型不支持 backface-visibility: hidden,所以这使用opacity来控制显隐 */
.front {
opacity: 0;
animation: o-1 1s 1s forwards;
@keyframes o-1 {
from {opacity: 0;}
to { opacity: 1; }
}
}
.back {
animation: o-0 1s 1s forwards;
/* transform: rotateY(360deg); */
@keyframes o-0 {
from {opacity: 1;}
to { opacity: 0; }
}
}
@keyframes flipAnimation {
from {transform: rotateY(180deg);}
to {transform: rotateY(360deg);}
}
}
/* 以下都是扭蛋机相关的动画 */
.lottery-box .lottery-box-content{
position: absolute;
top: 390rpx;
left: 92rpx;
z-index: 99;
width: 566rpx;
height: 680rpx;
overflow: hidden;
.qiu {
position: absolute;
display: block;
width: 100rpx;
height: 100rpx;
}
/* 各个球的定位 */
.qiu_0 { top: 483rpx; left: 430rpx; }
.qiu_1 { top: 384rpx; left: 32rpx; }
.qiu_2 { top: 482rpx; left: 23rpx; }
.qiu_3 { top: 580rpx; left: 10rpx; }
.qiu_4 { top: 438rpx; left: 115rpx; }
.qiu_5 { top: 500rpx; left: 186rpx; }
.qiu_6 { top: 542rpx; left: 100rpx; }
.qiu_7 { top: 458rpx; left: 278rpx; }
.qiu_8 { top: 580rpx; left: 248rpx; }
.qiu_9 { top: 577rpx; left: 462rpx; }
.qiu_10 { top: 537rpx; left: 340rpx; }
.qiu_11 { top: 480rpx; left: 333rpx; }
.diaol_0{animation:dropOut 1s linear 1.4s backwards;}
.diaol_0::after{animation-delay:1.3s;}
.diaol_1{animation:dropOut 1s linear 0.9s backwards;}
.diaol_1::after{animation-delay:0.8s;}
.diaol_2{animation:dropOut 1s linear 0.6s backwards;}
.diaol_2::after{animation-delay:0.5s;}
.diaol_3{animation:dropOut 1s linear  backwards;}
.diaol_4{animation:dropOut 1s linear 1.1s backwards;}
.diaol_4::after{animation-delay:1s;}
.diaol_5{animation:dropOut 1s linear 0.8s backwards;}
.diaol_5::after{animation-delay:0.7s;}
.diaol_6{animation:dropOut 1s linear 0.4s backwards;}
.diaol_6::after{animation-delay:0.3s;}
.diaol_7{animation:dropOut 1s linear 0.9s backwards;}
.diaol_7::after{animation-delay:0.8s;}
.diaol_8{animation:dropOut 1s linear 0.6s backwards;}
.diaol_8::after{animation-delay:0.5s;}
.diaol_9{animation:dropOut 1s linear 1.1s backwards;}
.diaol_9::after{animation-delay:1s;}
.diaol_10{animation:dropOut 1s linear 0.2s backwards;}
.diaol_11{animation:dropOut 1s linear 1.4s backwards;}
.diaol_11::after{animation-delay:1.3s;}
@keyframes dropOut {
0% { transform: translateY(-200%); opacity: 0; }
5% { transform: translateY(-200%); }
15% { transform: translateY(0); }
30% { transform: translateY(-100%); }
40% { transform: translateY(0%); }
50% { transform: translateY(-60%); }
70% { transform: translateY(0%); }
80% { transform: translateY(-30%); }
90% { transform: translateY(0%); }
95% { transform: translateY(-14%); }
97% { transform: translateY(0%); }
99% { transform: translateY(-6%); }
100% { transform: translateY(0); opacity: 1; }
}
.run_0 {animation:around0  1.5s linear  infinite;}
.run_1 {animation:around1  1.5s linear  infinite;}
.run_2 {animation:around2  1.5s linear  infinite;}
.run_3 {animation:around3  1.5s linear  infinite;}
.run_4 {animation:around4  1.5s linear  infinite;}
.run_5 {animation:around5  1.5s linear  infinite;}
.run_6 {animation:around6  1.5s linear  infinite;}
.run_7 {animation:around7  1.5s linear  infinite;}
.run_8 {animation:around8  1.5s linear  infinite;}
.run_9 {animation:around9  1.5s linear  infinite;}
.run_10{animation:around10 1.5s linear  infinite;}
.run_11{animation:around11 1.5s linear  infinite;}
/* 移动范围 left 0-464 ;  top 0-536 */
/* 这里解释一下为什么小球变换要用定位而不是更省性能transform: 因为部分机型对transform支持性不好,会被过滤掉(比如ios上transform2d不生效,1加ace2对transform单项变换不生效)因此这种关键动画使用定位来执行 */
@keyframes around0 {
0%    { top: 483rpx; left: 430rpx; }
20%   {   top: 536rpx; left: 0rpx; }
40%   { top: 0rpx; left: 464rpx;   }
60%   {   top: 200rpx; left: 0rpx; }
80%   { top: 3rpx; left: 303rpx;   }
100%  { top: 483rpx; left: 430rpx; }
}
@keyframes around1 {
0%    {  top: 384rpx; left: 32rpx; }
16%   { top: 200rpx; left: 450rpx; }
32%   {   top: 24rpx; left: 8rpx;  }
48%   { top: 500rpx; left: 106rpx; }
64%   {   top: 6rpx; left: 0rpx;   }
82%   { top: 180rpx; left: 464rpx; }
100%  {  top: 384rpx; left: 32rpx; }
}
@keyframes around2 {
0%    {  top: 482rpx; left: 23rpx; }
20%   { top: 64rpx; left: 448rpx;   }
40%   {  top: 520rpx; left: 16rpx;  }
60%   { top: 208rpx; left: 432rpx;  }
80%   {  top: 8rpx; left: 80rpx;    }
100%  {  top: 482rpx; left: 23rpx;  }
}
@keyframes around3 {
0%    {  top: 580rpx; left: 10rpx; }
20%   { top: 100rpx; left: 300rpx;  }
40%   {  top: 20rpx; left: 10rpx;   }
60%   { top: 400rpx; left: 460rpx;  }
80%   { top: 68rpx; left: 220rpx;   }
100%  {  top: 580rpx; left: 10rpx;  }
}
@keyframes around4 {
0%    { top: 438rpx; left: 115rpx; }
16%   { top: 10rpx; left: 300rpx;  }
32%   {  top: 530rpx; left: 30rpx;  }
48%   { top: 200rpx; left: 450rpx;  }
64%   {  top: 300rpx; left: 20rpx;  }
82%   { top: 560rpx; left: 450rpx;  }
100%  { top: 438rpx; left: 115rpx;  }
}
@keyframes around5 {
0%    { top: 500rpx; left: 186rpx; }
20%   {  top: 200rpx; left: 50rpx;  }
40%   { top: 350rpx; left: 400rpx;  }
60%   { top: 530rpx; left: 100rpx;  }
80%   { top: 100rpx; left: 380rpx;  }
100%  { top: 500rpx; left: 186rpx;  }
}
@keyframes around6 {
0%    {  top: 542rpx; left: 100rpx; }
15%   {  top: 300rpx; left: 300rpx;  }
30%   {  top: 100rpx; left: 100rpx;  }
45%   {  top: 200rpx; left: 400rpx;  }
60%   {  top: 400rpx; left: 200rpx;  }
75%   {  top: 100rpx; left: 450rpx;  }
100%  {  top: 542rpx; left: 100rpx;  }
}
@keyframes around7 {
0%    {  top: 458rpx; left: 278rpx; }
15%   {   top: 200rpx; left: 50rpx;  }
35%   {  top: 150rpx; left: 450rpx;  }
55%   {   top: 520rpx; left: 50rpx;  }
75%   {  top: 250rpx; left: 450rpx;  }
90%   {   top: 530rpx; left: 20rpx;  }
100%  {  top: 458rpx; left: 278rpx;  }
}
@keyframes around8 {
0%    {  top: 580rpx; left: 248rpx;  }
20%   {   top: 350rpx; left: 50rpx;  }
40%   {  top: 10rpx; left: 460rpx;   }
60%   {  top: 536rpx; left: 460rpx;  }
80%   {  top: 20rpx; left: 380rpx;   }
100%  {  top: 580rpx; left: 248rpx;  }
}
@keyframes around9 {
0%    {  top: 577rpx; left: 462rpx; }
12.5% {  top: 400rpx; left: 300rpx;  }
25%   {  top: 450rpx; left: 500rpx;  }
37.5% {  top: 350rpx; left: 200rpx;  }
50%   {  top: 250rpx; left: 450rpx;  }
62.5% {  top: 400rpx; left: 150rpx;  }
75%   {  top: 150rpx; left: 350rpx;  }
87.5% {  top: 500rpx; left: 250rpx;  }
100%  {  top: 577rpx; left: 462rpx;  }
}
@keyframes around10 {
0%    {  top: 537rpx; left: 340rpx;  }
15%   {  top: 400rpx; left: 150rpx;  }
30%   {  top: 350rpx; left: 450rpx;  }
50%   {   top: 50rpx; left: 50rpx;   }
70%   {  top: 450rpx; left: 400rpx;  }
85%   {  top: 550rpx; left: 120rpx;  }
100%  {  top: 537rpx; left: 340rpx;  }
}
@keyframes around11 {
0%   { top: 480rpx; left: 333rpx; }
16%  { top: 350rpx; left: 464rpx; }
33%  { top: 400rpx; left: 200rpx; }
50%  { top: 200rpx; left: 400rpx; }
66%  { top: 300rpx; left: 100rpx; }
83%  { top: 100rpx; left: 300rpx; }
100% { top: 480rpx; left: 333rpx; }
}
.ball_0{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball0.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_1{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball1.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_2{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball2.png');
background-repeat: no-repeat;
background-size: 100%;
}
.ball_3{
background-image: url('https://gitee.com/jingkunxu/img/raw/master/note/blog/ball3.png');
background-repeat: no-repeat;
background-size: 100%;
}
}

本站无任何商业行为
个人在线分享 » 前端小程序,手把手教你从零开始做一个酷炫的扭蛋机十连抽动画效果
E-->