最近新增一个抽奖小模块,就是扭蛋机的形式,产品给了参考网页,奈何不好扒下来用,只得自己动手干了,不多bb,先看效果吧!

效果图:

动画分析

由上面gif可看出,整个动画分为个部分

  1. 扭蛋随机(也不算随机吧)在固定盒子内跳动
  2. 中奖扭蛋下落
  3. 中奖扭蛋移动到中心,并且逐渐放大
  4. 中奖扭蛋做出扭开姿势,缓慢打开

整个过程分析好了,貌似还不难,那就一步一步来实现

实现步骤一,盒子内随机跳动

在实现跳动前,先要做的一步是,尽可能把蛋摆放的随机自然一点,怎么做?当然是定位啦。 我比较懒,于是计算了大概边界位置(我将整个球的摆放,分为三层,第一层,当然是贴近盒子边缘,第二层就再其上方了,第三层类推,同时再找好左右边界位置)

初始位置计算:

// 这里用的是vue框架,扭蛋是通过v-for渲染出来的
computed: {//动态绑定stylecalcStyle() {return function (index) {let top = index < 4 ? ( [1,2].includes( index ) ? '78%' : '71%')  : (  index < 8 ? ( [5,6].includes(index) ? '61%' : `${getRandomArbitrary(54,56)}%` ) : `${getRandomArbitrary(45,46)}%`);return {width: '18%',transform: `rotate(${getRandomArbitrary(8,20) * 15}deg)`,top}}}
},
// 生成随机数
export const getRandomArbitrary = (min = 0, max)=> {min = Math.ceil(min)max = Math.floor(max)return Math.floor(Math.random() * (max - min + 1)) + min //含最大值,含最小值
}

随机跳动,其实就是写好的动画,需要时只需添加上即可

// 其中一个动画
@keyframes move1 {0% {transform: rotate(-30deg);left: 12.7%;top: 57.9%;}26% {transform: rotate(60deg);left: 41.2%;top: 8.9%;}44% {transform: rotate(110deg);left: 52.2%;top: 21.8%;}64% {transform: rotate(56deg);left: 72%;top: 38%;}100% {transform: rotate(-30deg);left: 12.7%;top: 57.9%;}
}

添加动画

itemBoxStyle.animation = `move$1 0.75s 6 linear`

实现步骤二,扭蛋下落

下落动画不难,定义好初始位置,和结束位置,同样添加合适动画就可以了
tips: 要注意一个问题,开始扭蛋是看不见的(可能需要定位层级改变),然后下落一定高度扭蛋可以看见了(我用 overflow: hidden; 去解决)
css:

/* 下降动画 */
@keyframes upInDown {0% {opacity: 0;}100% {opacity: 1;top: 43%;}
}

js添加:

resNode.classList.add('resulteDown')

实现步骤三,扭蛋移动到中心

要实现扭蛋移动到中心,并且逐渐放大,整个动画看似复杂,其实看你的思路,由于接触了之前的 flip思想,不懂的可以看之前的文章,只需知道中心位置起始位置就可以计算出平移量,其他的就是细节处理
中心位置计算:

// 这里我采取先将其定位到中心位置,然后在获取位置,建议在加载时,就计算好
getEggEndLocation() {const eggEnd = this.$refs.hitEggconst style = {position: 'fixed',top: '50%',left: '50%',transform: 'translate(-50%, -50%) scale(1.8)','z-index': '-1',opacity: 0}for (const key in style) {if (Object.hasOwnProperty.call(style, key)) {eggEnd.style[key] = style[key]}}this.lastSite = this.getEleLocation(eggEnd) // 清除设置for (const key in style) {if (Object.hasOwnProperty.call(style, key)) {eggEnd.style[key] = ''}}
},
// 获取元素位置
getEleLocation(ele) {const { top,left } = ele.getBoundingClientRect()return { top,left }
},

初始位置,直接用 getEleLocation 就可以了,有了起始和结束位置,就可以计算动画过程了

resNode.style.transform = `translate3d(${ this.lastSite.left - left }px, ${ this.lastSite.top - top }px,0px) scale(${1.8}) rotate(-45deg)`
resNode.classList.add('active2')
.active2{transition: all 1.4s linear;
}

实现步骤四,扭一扭,然后打开

这一步就全是动画了,就不过多叙说

@keyframes upOpen {0% {transform: translate(0px,0px);}25% {transform: translate(5px,0px);}50% {transform: translate(-5px,0px);}70% {transform-origin: -10% 85%;transform: translate(0px,0px) rotate(0deg);}100% {transform: rotate(-30deg);transform-origin: -10% 85%;}
}@keyframes bottomOpen {0% {transform: translate(0px,0px);}25% {transform: translate(-5px,0px);}50% {transform: translate(5px,0px);}70% {transform-origin: 6% 16%;transform: translate(0px,0px) rotate(0deg);}100% {transform: rotate(30deg);transform-origin: 6% 16%;}
}

最后就动画效果的复位,删除添加的class就可以了

全部代码:

<template><div><div class="gashapon" ><button @click="toStart" >点击抽奖</button><!-- 蛋区 --><div class="egg_area" ><div ref="eggBody" ><div class="egg_box" v-for="(item,index) in imgIndex" :style="calcStyle(index)":class="`egg_box${index+1}`":key="index"><img :src="require(`../../../assets/egg/egg${item}.png`)"  alt=""/></div></div></div><!-- 出口 --><div class="hit_box" ref="hitEggBox"><!-- 出口蛋 --><div class="hit_egg" ref="hitEgg" ><!-- 光 --><div class="light_box" v-show="lightShow" ><img class="light_img" src="../../../assets/egg/e_sun.jpg" alt=""></div><img  src="../../../assets/egg/egg_top.png" alt=""><img src="../../../assets/egg/egg_foot.png" alt=""></div></div></div><van-overlay :show="show" :lock-scroll="true"  /></div>
</template><script>import { getRandomArbitrary } from '../../../utils/lib'import {Overlay,} from "vant"export default {name: 'EggMachine',components: {[Overlay.name]: Overlay,},data() {return {imgIndex: [1, 2, 3, 2, 2, 3, 1, 1, 2, 1],moveIng: false,lastSite: {},show: false,lightShow: false}},created() {},async mounted() {this.$nextTick(() => {// 获取中心位置setTimeout(() => {this.getEggEndLocation()},400)})},computed: {calcStyle() {return function (index) {let top = index < 4 ? ( [1,2].includes( index ) ? '78%' : '71%')  : (  index < 8 ? ( [5,6].includes(index) ? '61%' : `${getRandomArbitrary(54,56)}%` ) : `${getRandomArbitrary(45,46)}%`);return {width: '18%',transform: `rotate(${getRandomArbitrary(8,20) * 15}deg)`,top}}}},methods: {async toStart() {this.setNodeClass(true)await this.delay(1500)this.setNodeClass(false)// 页面滚动到顶部,保证动画在中window.scroll(0,0)// 下降this.eggDown()},// 节点class处理async setNodeClass(add = true) {const eggChild = this.$refs.eggBody.childNodesfor (let i = 0; i < 10; i++) {const itemBoxStyle = eggChild[i].styleadd ? itemBoxStyle.animation = `move${i+1} 0.75s 6 linear` : itemBoxStyle.animation = ''} this.moveIng = add},// 下降async eggDown() {const resNode = this.$refs.hitEggthis.show = trueresNode.style.zIndex = '2'resNode.classList.add('resulteDown')await this.delay(1000)// 记录当前位置const { top,left } = resNode.getBoundingClientRect()// 设置转变this.$refs.hitEggBox.style.overflow = 'visible'if(!Object.keys(this.lastSite).length) {this.getEggEndLocation()}resNode.style.transform = `translate3d(${ this.lastSite.left - left }px, ${ this.lastSite.top - top }px,0px) scale(${1.8}) rotate(-45deg)`resNode.classList.add('active2')await this.delay(1800)this.openEgg()},// 获取扭蛋结束中心位置getEggEndLocation() {const eggEnd = this.$refs.hitEggconst style = {position: 'fixed',top: '50%',left: '50%',transform: 'translate(-50%, -50%) scale(1.8)','z-index': '-1',opacity: 0}for (const key in style) {if (Object.hasOwnProperty.call(style, key)) {eggEnd.style[key] = style[key]}}this.lastSite = this.getEleLocation(eggEnd) // 清除设置for (const key in style) {if (Object.hasOwnProperty.call(style, key)) {eggEnd.style[key] = ''}}},// 打开动画async openEgg() {// 测试const resNode = this.$refs.hitEggconst resNodeImg = resNode.childNodes// 添加打开动画resNodeImg[1].classList.add('eggOpenTop')resNodeImg[2].classList.add('eggOpenBottom')await this.delay(900)this.lightShow = trueawait this.delay(900)// console.log('抽奖结束')this.$emit('darw-succes')// 复位this.$refs.hitEggBox.style.overflow = 'hidden'resNodeImg[1].classList.remove('eggOpenTop')resNodeImg[2].classList.remove('eggOpenBottom')resNode.classList.remove('resulteDown')resNode.classList.remove('active2')resNode.style.transform = ''resNode.style.zIndex = ''this.show = falsethis.lightShow = false},// 获取元素位置getEleLocation(ele) {const { top,left } = ele.getBoundingClientRect()return { top,left }},// 延迟函数async delay(time = 2000) {return new Promise((res) => {setTimeout(() => {res()},time)})},}}
</script><style lang="less" scoped>.active2{transition: all 1.4s linear;}.eggOpenTop {animation: upOpen 1.2s linear;animation-fill-mode: forwards;}.eggOpenBottom {animation: bottomOpen 1.2s linear;animation-fill-mode: forwards;}img{width: 100%;}.gashapon{min-height: 8rem;background: url('../../../assets/egg/gashapon.png') no-repeat center;background-size: 100% 100%;position: relative;}.egg_area{position: absolute;left: 54.5%;transform: translateX(-50%);width: 5.2rem;height: 4.5rem;background-color: transparent;border-radius: 50%;top: 0.1rem;z-index: 1;}.egg_box {position: absolute;}.egg_box img {width: 100%;}.hit_egg{position: absolute;width: 0.8rem;top: -80%;left: 49%;transform: rotate(-45deg) translateX(-50%);transform-origin:50% 50%;img{width: 100%;&:nth-child(3){margin-top: -0.1rem;}}.light_box{position: absolute;width: 1rem;overflow: hidden;top: -0.1rem;.light_img{animation: rotateAni 0.8s infinite linear;}}}.hit_box{position: absolute;width: 1.6rem;height: 2rem;top: 71%;left: 29%;overflow: hidden;}.resulteDown {animation: upInDown 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000);animation-fill-mode: forwards;}/* ------- 10个蛋 -------   */ /* 前4个 */.egg_box1 {left: 16%;}.egg_box2 {left: 32%;}.egg_box3 {left: 48%;}.egg_box4 {left: 64%;}/* 后四个 */.egg_box5 {left: 21%;}.egg_box6 {left: 34%;}.egg_box7 {left: 48%;}.egg_box8 {left: 60%;}/* 后两个 */.egg_box9 {left: 48%;}.egg_box10 {left: 37%;}// 放大动画@keyframes rotateAni {0%{transform: scale(0.9);}100% {transform: scale(1.1);}}
</style>
<style>
/* 打开动画 */
@keyframes upOpen {0% {transform: translate(0px,0px);}25% {transform: translate(5px,0px);}50% {transform: translate(-5px,0px);}70% {transform-origin: -10% 85%;transform: translate(0px,0px) rotate(0deg);}100% {transform: rotate(-30deg);transform-origin: -10% 85%;}
}@keyframes bottomOpen {0% {transform: translate(0px,0px);}25% {transform: translate(-5px,0px);}50% {transform: translate(5px,0px);}70% {transform-origin: 6% 16%;transform: translate(0px,0px) rotate(0deg);}100% {transform: rotate(30deg);transform-origin: 6% 16%;}
}/* 下降动画 */
@keyframes upInDown {0% {opacity: 0;}100% {opacity: 1;top: 43%;}
}/* 蛋滚动 */
@keyframes move1 {0% {transform: rotate(-30deg);left: 12.7%;top: 57.9%;}26% {transform: rotate(60deg);left: 41.2%;top: 8.9%;}44% {transform: rotate(110deg);left: 52.2%;top: 21.8%;}64% {transform: rotate(56deg);left: 72%;top: 38%;}100% {transform: rotate(-30deg);left: 12.7%;top: 57.9%;}
}@keyframes move2 {0% {transform: rotate(85deg);left: 31.2%;top: 57.9%;}23% {transform: rotate(210deg);left: 70%;top: 36%;}45% {transform: rotate(120deg);left: 45%;top: 8%;}72% {transform: rotate(30deg);left: 8%;top: 34%;}100% {transform: rotate(85deg);left: 31.2%;top: 57.9%;}
}@keyframes move3 {0% {transform: rotate(-10deg);left: 50%;top: 57.9%;}38% {transform: rotate(-30deg);left: 38%;top: 11.4%;}65% {transform: rotate(-50deg);left: 7%;top: 38.7%;}100% {transform: rotate(-10deg);left: 50%;top: 57.9%;}
}@keyframes move4 {0% {transform: rotate(20deg);left: 65%;top: 59.9%;}35% {transform: rotate(-30deg);left: 53.4%;top: 11.3%;}64% {transform: rotate(-53deg);left: 24.3%;top: 56%;}100% {transform: rotate(20deg);left: 65%;top: 59.9%;}
}@keyframes move5 {0% {transform: rotate(-65deg);left: 61.4%;top: 38%;}29% {transform: rotate(-180deg);left: 40%;top: 11.5%;}53% {transform: rotate(-222deg);left: 9%;top: 41.3%;}76% {transform: rotate(-160deg);left: 21.8%;top: 57.9%;}100% {transform: rotate(-65deg);left: 61.4%;top: 38%;}
}@keyframes move6 {0% {transform: rotate(16deg);left: 44.2%;top: 42%;}28% {transform: rotate(-60deg);left: 18%;top: 57%;}40% {transform: rotate(-45deg);left: 8%;top: 41.3%;}80% {transform: rotate(70deg);left: 52.7%;top: 9.9%;}100% {transform: rotate(16deg);left: 44.2%;top: 42%;}
}@keyframes move7 {0% {transform: rotate(-13deg);left: 27.5%;top: 39.9%;}17% {transform: rotate(50deg);left: 37.5%;top: 57.9%;}44% {transform: rotate(75deg);left: 75%;top: 41.3%;}67% {transform: rotate(42deg);left: 50.18%;top: 8%;}100% {transform: rotate(-13deg);left: 27.5%;top: 39.9%;}
}@keyframes move8 {0% {transform: rotate(46deg);left: 14.4%;top: 33.9%;}20% {transform: rotate(97deg);left: 45.6%;top: 7.8%;}45% {transform: rotate(143deg);left: 76.8%;top: 41.6%;}65% {transform: rotate(85deg);left: 64.6%;top: 57%;}100% {transform: rotate(46deg);left: 14.4%;top: 33.9%;}
}@keyframes move9 {0% {transform: rotate(65deg);left: 36.4%;top: 20%;}41% {transform: rotate(130deg);left: 74.3%;top: 42.9%;}76% {transform: rotate(94deg);left: 46.5%;top: 57.9%;}100% {transform: rotate(65deg);left: 36.4%;top: 20%;}
}@keyframes move10 {0% {transform: rotate(-92deg);left: 53.6%;top: 22.11%;}20% {transform: rotate(-142deg);left: 37%;top: 58.5%;}47% {transform: rotate(-198deg);left: 6.7%;top: 37.3%;}67% {transform: rotate(-135deg);left: 23%;top: 10.7%;}100% {transform: rotate(-92deg);left: 53.6%;top: 22.11%;}
}
</style>

ps: 7/22 修复一个bug

由于笔者在小球 style 处采用了绑定 calcStyle 方法,vue在更新dom时会调用这个方法,导致这个方法会运行多次,小球位置也随之发生变化,暂时采取如下方式

    data() {return {// data 处新增缓存变量styleCacheMap: {}}},computed: {calcStyle() {return function (index) {// 作一次缓存处理if(Object.keys(this.styleCacheMap).length && this.styleCacheMap[index]) {return this.styleCacheMap[index]}let top = index < 4 ? ( [1,2].includes( index ) ? '78%' : '71%')  : (  index < 8 ? ( [5,6].includes(index) ? '61%' : `${getRandomArbitrary(54,56)}%` ) : `${getRandomArbitrary(45,46)}%`);this.styleCacheMap[index] = {width: '18%',transform: `rotate(${getRandomArbitrary(8,20) * 15}deg)`,top}return this.styleCacheMap[index]}}},

前端搞一个扭蛋抽奖小动画?相关推荐

  1. uniapp扭蛋抽奖

    uniapp扭蛋抽奖 看市面上没啥人写,然后vue的也比较乱,然后写了一个比较简单demo.拿走就可以用. 用了uni自带的动画,使用ios和安卓问题不大. 大家可以试试,个人感觉挺流畅的. < ...

  2. Java幸运盒子代码_幸运盒子扭蛋机小程序app开发

    互联网产品是植根于互联网大环境的产品,幸运盒子扭蛋机小程序app开发是互联网产品的基本生长土壤.互联网的思想.原则和方法,必然以类似于"基因"继承的形式在每一个具体互联网产品中得以 ...

  3. 想做盲盒扭蛋机小程序-如何选择服务商

    如果是选择盲盒扭蛋机小程序模板开发服务商,模板所提供的功能原原不足以满足多元化玩法,甚至会变成一种商品商城销售模式. 所以如果需要选择小程序的话一定要选择有成功的盈利玩法案例的服务商来提供版本体验. ...

  4. css3扭蛋机,微信小程序 扭蛋抽奖机CSS3动画实现详解_咋地 _前端开发者

    前言 最近快速上线一个抽奖活动,又不想用canvas做,思考了很久,还是决定使用css3的动画来做,只要小球动得快,就没人发现我这些个小球的运动路径都是一样的了.先上动图 wxml文件: 这个做得我头 ...

  5. PHP扭蛋机原理,微信小程序 扭蛋抽奖机css3动画实现详解

    前言 最近快速上线一个抽奖活动,又不想用canvas做,思考了很久,还是决定使用css3的动画来做,只要小球动得快,就没人发现我这些个小球的运动路径都是一样的了.先上动图 wxml文件: 这个做得我头 ...

  6. css3扭蛋机,微信小程序扭蛋抽奖机css3动画实现详解.pdf

    微微信信小小程程序序 扭扭蛋蛋抽抽奖奖机机css3动动画画实实现现详详解解 前前言言 最近快速上线一个抽奖活动,又不想 canvas做,思考了很久,还是决定使 css3的动画来做,只要小球动得快,就没 ...

  7. 设计-来做一个Windows hello的小动画

    今天想和大家分享的是一个Windows hello的动画,也就是这个笑脸的效果. 当时是因为一个小伙伴在交流群里问了一下这个效果是怎么制作的,我觉得还挺有意思的,就觉得复刻一下,顺便出一期教程来讲讲其 ...

  8. 盲盒扭蛋机小程序-系统简介

    为什么是小程序,而不是APP? 目前国内小程序行业技术非常成熟,百度.抖音.腾讯等平台的小程序兼容性扩展性都非常好. 相对APP的灵活性和实用性就比不上小程序.所以在选择用户终端上,更倾向于即用即走的 ...

  9. 【Python系列】一个简单的抽奖小程序

    序言 很开心你能在万千博文中打开这一篇,希望能给你带来一定的帮助!

最新文章

  1. dependent-name ‘xxx::yyy’ is parsed as a non-type, but instantiation yields a type
  2. 运维工程师必备之MySQL数据的主从复制、半同步复制和主主复制详解
  3. call指令和ret指令的配合使用
  4. 吴恩达深度学习代码_吴恩达深度学习笔记(58)-深度学习框架Tensorflow
  5. 升级 90天 vs2008 在win2008下。
  6. linux下Intellij Idea 14的安装
  7. linux dns 攻击,DNSlog攻击技巧 | CN-SEC 中文网
  8. 在ArcGIS调坐标系引发的一系列问题
  9. high definition audio感叹号_【网抑云文案】你知道红色感叹号吧,我对着它聊了184天。...
  10. 2019年,微信营销软件排行榜
  11. 中国象棋大战 瑞星杀毒软件序列号
  12. 腾讯音乐12月初赴美IPO,250亿美元被低估还是高估?
  13. 性能测试--11Beanshell的脚本开发技术
  14. 基于STM32居家加湿器控制仿真设计-基于STM32热释人体感应智能门禁报警设计-基于STM32无刷电机BLDC速度控制器设计-基于STM32智能路灯灯光自动控制设计-基于单片机PID控制算法开关电源
  15. matlab命令窗口双大于号不显示了 回车命令不管用了,怎么改才能恢复
  16. 如何查看CDR文件是出自哪个版本?
  17. HTTP协议和web服务技术---Apche配置
  18. CSPJ2019T4(加工零件)题解
  19. requests发送post请求到金山翻译
  20. 如何利用开源插件?又快又好地搞好数据接口开发,连通不同应用系统

热门文章

  1. (Spring+SpringMVC+mybatis)SSM选课管理系统/课程管理系统 完整项目介绍
  2. sql大全超详细【转】
  3. 职业教育计算机教学,浅析职业教育中的计算机教学思考.doc
  4. AI服务官上线“一网通办”:找政府办事就像逛网店
  5. python 打开是黑的-python黑色
  6. 3.2 人工智能关键技术
  7. 所谓资本寒冬,不过是一厢情愿的破灭
  8. python对人工智能的看法_为什么人工智能用python
  9. 帮助海外游客规划从成田机场开始日本旅行的新网站上线
  10. h5 先加载小图_干货!高手珍藏版的H5秘密尺寸