最近工作中有个9宫格抽奖的需求,一开始没啥思绪,网上查阅一番,自己又修改了一下,希望与大家交流分享。
先看一下效果图
整理一下思路 利用flex布局形成9宫格,每个格子的序号为0~8,创建一个记录当前指针的变量,点击抽奖,指正转动,当index等于变量的时候让当前格子与其他格子样式不一致,当请求后台有结果时,让指针等于结果值。
上图中黑色数组代表奖品元素排列的顺序,其中4为抽奖按钮,红色数字则为指针转动的路线。
上代码:
首先定义几个变量
constructor (props) {
super(props)
this.state = {
timer1: '', // 定时器1 快速旋转
timer2: '', // 定时器2 慢速旋转
prizeIndex: 0, // 转动指针
stopIndex: null, // 抽中奖品
arrNum: [0, 1, 2, 5, 8, 7, 6, 3], // 转动顺序
luckList: [], // 奖品集合
isRolling: false // 是否正在抽奖
}
// 这边绑定是必要的,这样 `this` 才能在回调函数中使用
this.drawPrize = this.drawPrize.bind(this);
this.move = this.move.bind(this);
this.lowSpeed = this.lowSpeed.bind(this);
}
然后根据数据写DOM渲染的条件
因为后台返回数据只有8条奖品,所以再获取奖品集合时对数据做一些改变,这个根据实际情况而定
res.data.dtls.splice(4, 0, {prizeName: "抽奖按钮", type: 0});
这里利用type属性对奖品和抽奖按钮做区分
renderLi = () => {
let {luckList} = this.state;
return luckList.map((list, index) => {
if (list.type !== 0) {
return <div key={index} className={list.chose === 1 ? 'chose box_prize' : 'box_prize'}>
{list.type===1||list.type===2?
<img alt="" src={require('./css/redpocket.png')}/> :
list.type===3?
<img alt="" src={require('./css/integral.png')}/> : ""
}
<div className="name">{list.prizeName}</div>
</div>
} else {
return <div key={index} className='box_prize click' onClick={this.drawPrize}></div>
}
})
}
<div className="luck_turntable">
{this.renderLi()}
</div>
点击抽奖按钮运行函数drawPrize
// 点击抽奖 执行快速转动,2秒后执行慢速转动
async drawPrize() {
if (!this.state.isRolling) {
this.setState({
prizeIndex: 0,
stopIndex: null,
isRolling: true,
timer1: setInterval(this.move, 100)
});
setTimeout(() => { //转一圈半之后降速
clearInterval(this.state.timer1);
this.lowSpeed()
}, 2000)
}
}
慢速转动
lowSpeed() {//慢速转动
// 先清除快速转动的定时器
clearInterval(this.state.timer1);
// 请求接口,获取转动结果
api.lotteryId(this.state.lotteryId, token).then(res => {
if(res.resultCode === '0') {
let stopIndex = null
// 对转动结果做处理
switch (res.data) {
case 4:
stopIndex = 3
break;
case 7:
stopIndex = 4
break;
case 6:
stopIndex = 5
break;
case 5:
stopIndex = 6
break;
case 3:
stopIndex = 7
break;
case 0:
stopIndex = 0
break;
case 1:
stopIndex = 1
break;
case 2:
stopIndex = 2
break;
default:
stopIndex = null
break
}
// 得到结果再起调用move函数,速度降低
this.setState({
stopIndex: stopIndex,
timer2: setInterval(this.move, 300)
});
}
}
执行转动函数move
move() {//转动
let luckList = this.state.luckList
let arrNum = this.state.arrNum
// chose=1为转动到位置,0为正常位置
luckList[arrNum[this.state.prizeIndex]].chose = 1
luckList[arrNum[this.state.prizeIndex-1<0?7:this.state.prizeIndex-1]].chose = 0
// 如果指针位置prizeIndex与结果位置相同,则抽奖完成,清除所有定时器
if (this.state.stopIndex !== null && (this.state.prizeIndex === this.state.stopIndex)) {
clearInterval(this.state.timer1);
clearInterval(this.state.timer2);
setTimeout(() => {
this.setState({
isRolling: false // 可以继续抽奖
})
}, 300);
}
// 否则,继续转动,这里需要对零界位置做一下处理
else {
this.setState({
prizeIndex: this.state.prizeIndex + 1 === 8 ? 0 : this.state.prizeIndex + 1
})
}
}
另外需要在第二次点击时恢复状态值,我是在其他条件下做的处理,这个根据实际情况而定。
好了,一个简单的9宫格抽奖就完成了,有更好的,更丰富的方案的请不吝赐教。
更新 function component 实现
最近在搭建组件库的时候,想到这也是个不错的功能性组件,于是想起用hook语法再实现一遍
type Res = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
interface IProps {
begin: () => Promise<Res>;
}
const SuperPrizeGrid = (props: IProps) => {
const timer1 = useRef();
const timer2 = useRef();
const [args, setArgs] = useState<{
prizeIdx: Res;
stopIdx: Res | null;
isRolling: boolean;
}>({
prizeIdx: 0,
stopIdx: null,
isRolling: false,
});
const [, forceUpdate] = useReducer(n => ++n, 0);
const move = useCallback(() => {
console.log(args);
// 当prizeIdx转到stopIdx的位置时停止
if (args.stopIdx !== null && args.prizeIdx === args.stopIdx) {
timer1.current && clearInterval(timer1.current);
timer2.current && clearInterval(timer2.current);
timer1.current = undefined;
timer2.current = undefined;
setTimeout(() => {
message.info(
`中奖了:${
data.filter(item => item.seat === args.stopIdx)[0].content
}`,
() => {
setArgs(
Object.assign(args, {
prizeIdx: 0,
stopIdx: null,
isRolling: false,
}),
);
forceUpdate();
},
);
}, 300);
return;
}
setArgs(
Object.assign(args, { prizeIdx: ((args.prizeIdx + 1) % 8) as Res }),
);
forceUpdate();
}, [args.stopIdx, args.prizeIdx]);
const lowSpeed = async () => {
timer1.current && clearInterval(timer1.current);
timer1.current = undefined;
//@ts-ignore
timer2.current = setInterval(move, 180);
// TODO
let res = await props.begin();
setArgs(Object.assign(args, { stopIdx: res }));
};
const clickPrize = () => {
if (!args.isRolling) {
setArgs(Object.assign(args, { isRolling: true }));
// @ts-ignore
timer1.current = setInterval(move, 80);
// 转2秒再请求数据
setTimeout(() => {
lowSpeed();
}, 3000);
}
};
return (
<div className="super-prize-grid">
{data.map((item, index) => (
<div
className={`item item-${index} ${
args.isRolling && map.get(index) === args.prizeIdx ? 'current' : ''
}`}
key={index}
onClick={() => index === 4 && clickPrize()}
>
{item.content}
</div>
))}
</div>
);
};
const data = [
{
seat: 0,
content: '红包1',
},
{
seat: 1,
content: '红包2',
},
{
seat: 2,
content: '优惠券1',
},
{
seat: 7,
content: '优惠券2',
},
{
seat: null,
content: '开始抽奖',
},
{
seat: 3,
content: '积分1',
},
{
seat: 6,
content: '积分2',
},
{
seat: 5,
content: '谢谢1',
},
{
seat: 4,
content: '谢谢2',
},
];
const map = new Map([
[0, 0],
[1, 1],
[2, 2],
[5, 3],
[8, 4],
[7, 5],
[6, 6],
[3, 7],
]);