摇奖机动效实现
效果
实现代码
import React,{FC, useEffect, useRef, useState} from "react"
import styles from './index.less'
import btn from '@/img/ernie/btn.png'
import icon1 from '@/img/ernie/icon1.png'
import icon2 from '@/img/ernie/icon2.png'
import icon3 from '@/img/ernie/icon3.png'
class LuckGame{
setting: {
//奖品个数
len: number;
//滚动时间
speed: number;
//循环几圈
circle: number;
};
$ul: any;
$height: any;
$gameItem: any;
canbegin:boolean;
constructor(obj: { len?: number; speed?: number; circle?: number; ul: any; item: any }) {
this.setting = {
//奖品个数
len : 3,
//滚动时间
speed : 5000,
//循环几圈
circle : 5
};
this.extend( this.setting, obj );
this.$ul = obj.ul
this.$gameItem = obj.item
this.$height = this.$gameItem[0]?.offsetHeight;//item的高度
this.canbegin=true
}
setList(imgArr: any[]){
//填充li
const List = imgArr.map((img,index)=>{
return <li key={index}><img src={img.img} style={{width:"70%"}}/></li>
})
const arr = new Array();
for(let n = 0;n <= this.setting.circle;n++){
arr.push(List)
};
return arr.map(item=>{
return item;
});
}
// 初始时设置图片随机位置
randomPosition(){
// 随机生成10-110的一个随机数
const num = Math.floor(Math.random()*110+10);
const arr = num.toString().split("");
if(arr.length===2){
const number = +arr[0]-1
arr.push(number+"");
}
[].forEach.call(this.$ul,(ulItem,index)=>{
//@ts-ignore
ulItem.style['transform']=ulItem.style['transform'] = `translate(0px, -${+arr[index]% this.setting.len * this.$height}px)`;
});
}
start(arr:[],fn?:any){
this.canbegin =false
let that = this;
let count = 0;
//开始抽奖
[].forEach.call(that.$ul,(o,i)=>{
setTimeout(()=>{
const y=(arr[i]+that.setting.len *(that.setting.circle-1))*that.$height;
//@ts-ignore
o.style['transition'] = `${that.setting.speed}ms ease`;
//@ts-ignore
o.style['transform'] = `translate(0px, -${y}px)`
},i * 300);
// @ts-ignore
o.addEventListener('transitionend',function(){
this.style['transition'] = '0ms ease';
this.style['transform'] = `translate(0px, -${arr[i]*that.$height}px)`;
count++
if(count===that.$ul.length){
fn&&fn()
}
},false);
})
}
// 中奖动画
getPrice(callback?:any){
let that = this;
[].forEach.call(that.$gameItem,(o,i)=>{
setTimeout(()=>{
switch(i){
case 0:
//@ts-ignore
o.style['animation'] = `${styles.iocnleft} 3s linear forwards`
break;
case 1:
//@ts-ignore
o.style['animation'] = `${styles.iocncenter} 2s linear forwards`
break;
case 2:
//@ts-ignore
o.style['animation'] = `${styles.iocnright} 3s linear forwards`
break;
default:break;
}
},300);
if(i===2){
//@ts-ignore
o.addEventListener("animationend", callback);
}
})
}
// 修改setting配置(外部传入的优先)
extend (n: { [x: string]: any; len?: number; speed?: number; circle?: number },n1: { [x: string]: any; len?: number | undefined; speed?: number | undefined; circle?: number | undefined; ul?: any; item?: any }){
for(let i in n1){n[i] = n1[i]};
}
}
const Ernie :FC<any> = ()=>{
const [game,setGame] = useState<any>()
useEffect(()=>{
document.documentElement.style.fontSize=(document.documentElement.clientWidth/750)*100+'px';
const newgame = new LuckGame({
len : 3,
speed : 3000,
circle : 5,
ul:gameUlRefList.current,
item:gameItemRefList.current
});
setGame(newgame)
},[])
useEffect(()=>{
if(game){
game.randomPosition()
}
},[game])
const reset = ()=>{
game.$gameItem.forEach((item:any)=>{
item.style["animation"] = "none"
})
// 设置随机图标显示
game.randomPosition()
}
const onAction = ()=>{
// 先请求接口判断是否中奖==>触发动效
if(!game.canbegin) return
console.log("开始触发动效")
game.start([1,1,1],()=>{
game.getPrice(()=>{
alert("弹出弹窗展示中奖结果")
reset()
game.canbegin = true
})
})
}
const gameUlRefList = useRef<any>([])
const getULRef = (dom: any) => {
if(!dom) return;
if(gameUlRefList.current.length>=3) return
gameUlRefList.current.push(dom)
}
const gameItemRefList = useRef<any>([])
const getItemRef = (dom: any) => {
if(!dom) return;
if(gameItemRefList.current.length>=3) return
gameItemRefList.current.push(dom)
}
return(
<div className={styles.mechine}>
{/* 摇奖机图案 */}
<div className={styles.game_wrap}>
{
[1,2,3].map((item)=>{
return <div className={styles.game_item} key={item} ref={getItemRef} >
<ul ref={getULRef}>
{game&&game.setList([{img:icon1},{img:icon2},{img:icon3}])}
</ul>
</div>
})
}
</div>
<div className={styles.btnContain}>
<img src={btn} className={styles.btn} onClick={onAction}/>
</div>
</div>
)
}
export default Ernie
.mechine{
width: 90%;
background-image: url(../../img/ernie/mechine.png);
background-repeat: no-repeat;
background-size: contain;
height: 90vh;
margin: auto;
.game_wrap{
width: 4.7rem;
height: 2.4rem;
overflow: hidden;
position: absolute;
top: 1.65rem;
left: 50%;
transform: translateX(-50%);
.game_item {
width: 33.333%;
height: 2.2rem;
display: inline-block;
}
.game_item ul{
padding: 0;
}
.game_item li {
list-style: none;
width: 100%;
height: 2.2rem;
line-height: 2.2rem;
text-align: center;
position: relative;
font-size: 50px;
}
}
.btn{
position: absolute;
width: 45%;
top: 4.4rem;
left: 50%;
transform: translateX(-50%);
}
}
// 抖动--->向中间靠拢并且透明度慢慢变为0
@keyframes iocnleft{
3%,5%,25%,10%{
transform: rotate(-3deg)
}
20%,15%,30%{
transform: rotate(3deg)
}
0%, 35%,40%{
transform: rotate(0)
}
90%{
opacity: 0.5;
}
100%{
transform: translateX(1.6rem);
opacity: 0;
}
}
// 抖动--->慢慢消失弹出礼品
@keyframes iocncenter{
3%,5%,25%,10%{
transform: rotate(-3deg)
}
20%,15%,30%{
transform: rotate(3deg)
}
0%, 35%,40%{
transform: rotate(0)
}
90%{
opacity: 1;
}
100%{
opacity: 0.3;
}
}
// 抖动--->向中间靠拢并且透明度变为0
@keyframes iocnright{
3%,5%,25%,10%{
transform: rotate(3deg)
}
20%,15%,30%{
transform: rotate(-3deg)
}
0%, 35%,40%{
transform: rotate(0)
}
90%{
opacity: 0.5;
}
100%{
transform: translateX(-1.6rem);
opacity: 0;
}
}
实现原理
第一部分:初始化(滚动圈数,速度,奖品个数:这里设置的是三个)
第二部分:填充奖品:根据滚动的圈数来判断需要填充多少元素在ul里面,方法看(setList)
第三部分:随机位置,因为默认都是从第一个奖品开始填充的,所以需要设置每个ul(理解成每个纵向)需要随机移动多少位置,具体实现方法看(randomPosition)
第四部分(重点):元素滚动:传入一个arr数组,表示第i个位置想要停留显示的是哪一个元素,例如arr = [1,1,1]表示1,2,3的位置都想要最后停留的是元素1,具体实现看start方法
滚动原理就是通过设置transition和transform
第五部分:中奖合成显示结果,看getPrice方法