写在前面,年会将至,需求自然也跟各种抽奖有关啦。最近刚好接了一个紧急的九宫格抽奖需求,顺便也记录一下撸这个简易九宫格的过程吧。
本文可能涉及以下内容:
- 九宫格布局
- 九宫格动效
- 抽奖逻辑处理
- 前后端联调
九宫格布局
九宫格大家应该都挺熟悉的吧,就是九个格子嘛,下面给大家看看我们线上的九宫格抽奖↓↓
等等..上面这个好像不是九个格子(它是DEMO)
这个布局相信大家都很熟悉吧,特别是看过阮一峰的童鞋们,是不是倍感亲切。
没错,我们这个布局是基于flex完成的,主要思路是纵横元素的分离。
- 整个九宫格区域应该是一个定宽高
(其实不定也无所谓)的块元素,将每一行(row)纵向排列。 - 每一行都是一个row,我们在row中将每个item块space-between或space-around(根据业务自行判断)。
- 每个item里面的内容也用flex水平居中一下,然后该怎么还原设计稿就怎么还原吧。
遇坑点,其实也不算坑,算是一个移动端自适应上的问题吧。
这里涉及到了rem布局的问题,由于某些历史原因,我们的项目并没有使用rem的方法来进行移动端页面的开发.
这就导致我们在开发某些需要高自适应的组件时比较蛋疼。但是最近在写其他项目的时候想到一个挺适合我们使用的方案,不过对浏览器、手机系统的版本可能有些许要求。
@function pxWithVw($n){ @return 100vw * $n / 375}@function pxWithVwMax($n){ @return 480px * $n / 375}@mixin pxVw2width($n) { width: pxWithVw($n); max-width: pxWithVwMax($n);}@mixin pxVw2height($n) { height: pxWithVw($n); max-height: pxWithVwMax($n);}复制代码
虽然我们没用rem布局,但是我们还是接了scss的,借用scss的mixin我们可以很爽的还原设计稿的各种参数,之所以还要限定一个Max值主要还是因为我们的项目支持在PC端查看,所以需要给它限定一个极限的宽度。
问题来了,pxVw2height里面为啥有个vw?其实就是个像素比例的问题,设计稿中宽度与设计稿中设备宽度的比例不就自然是每个px对应屏幕的比例了嘛。
不过这样写会有个不确定的地方,就是不同的DPR下,这种方法对像素还原是否有影响,关于这点我暂时无法确定,不过还是会抽空去试验一下的。
九宫格动效&抽奖逻辑处理
按理来说动效和抽奖逻辑是两码事,但是我们是在React里面开发的,我觉得有必要把它们放到一起讲。
动效核心
动效的触发核心是activedId的实时变更,通过定时器,在某一时间间隔内改变父组件state中的activedId,以达到九宫格中"蹬蹬蹬"的效果。
class RowItem extends React.Component { renderImgClass () { switch (this.props.content.raw_name) { ... } } render() { const { content, activedId } = this.props; return ({content.name}) }}复制代码
上面是每个小方块的源码,不难看出决定每个小方块该作何显示的地方这里
${activedId === content.id ? 'row__item row__item-active' : 'row__item'}复制代码
通过props传进来的activedId来决定轮到哪一个方块展示动效虽然我的九宫格那不叫动效,不过原理是互通的,想展示啥就尽管在这个组件里面整就好了。
抽奖逻辑
先梳理一下这类九宫格抽奖的流程,剥离那些各种附加的特效,其实本质就是随机(或指定)某个item的id为中奖id,然后我们围绕着这个id确定该循环转圈的次数,最后再确保中奖框能停留在指定id的item即可。
点击按钮抽奖这个过程,我将它分成了两个部分,一个是状态检测与归零,另一个是触发抽奖方法(其实也可以写在一起,个人习惯将其分离,自己看起来比较舒服)。
- handleBegin负责状态检测与归零
handleBegin() { if (!this.state.prizePlaying) { this.setState({ prizePlaying: true }) axios.post(url) .then(res => { if (res.data.code === 0) { ... axios.get(url2) .then(res => { if (res.data.code === 0) { this.setState({ ... }, () => { this.setState({ prizeActivedId: '', prizePrizeId: null, prizeTimes: 0, prizeActTimes: 0 }, () => { this.handlePlay() }) }) } }) } else { ... } }) }}复制代码
一开始先检测当前是否处于抽奖状态,如果不是才进行下面的请求,请求都完成后,将关于九宫格的状态都进行复原,然后才进行下一步操作。关于状态复原,个人认为这是相对便捷而且安全系数较高的做法,当然如果要保留原有的抽奖状态也是可以的,不过在用时候需要注意两个Times的关系。
- handlePlay真正的抽奖方法
handlePlay() { let prize; switch (this.state.prizeLottery) { prize = ... } this.setState({ prizePrizeId: prize, prizeActivedId: 0 }) let times = this.state.prizeList.length * Math.floor(Math.random() * 5 + 4) this.setState({ prizeTimes: times }) this.begin = setInterval(() => { let num; if (this.state.prizeActivedId === this.state.prizePrizeId && this.state.prizeActTimes > this.state.prizeTimes) { clearInterval(this.begin) ... this.setState({ prizePlaying: false }) return } if (this.state.prizeActivedId === '') { num = 0 this.setState({ prizeActivedId: num }) } else { num = this.state.prizeActivedId if (num === 7) { num = 0 this.setState({ prizeActivedId: num }) } else { num = num + 1 this.setState({ prizeActivedId: num }) } } this.setState({ prizeActTimes: this.state.prizeActTimes + 1 }) }, 100)}复制代码
确定中奖prizePrizeId,然后随机计算出一个最小的动画循环次数,然后就可以启动定时器开始动态更换prizeActivedId,随着prizePrizeId的变更RowItem也会重新渲染,也就成为了所谓的"蹬蹬蹬"效果啦。
如果看上面面的解释觉得不够详细,可以在文章末尾找到github的传送门,里面有demo源码并且有相关的注释。
前后端联调
这里主要想讲讲开始开发之前,自己对整个项目的规划(或想法),本着前端不可信原则,我们每次启动抽奖之前都应该与后端沟通,无论是次数还是最后的prizeId都不应该由前端决定。
从前端的角度说,我们大概需要两个接口:
- ① 类似init的接口,告诉我们的次数,以及是否已经有获奖
- ② 类似active的抽奖接口,我们告诉后端这里发起了一次抽奖 请求②接口之后接着返回给我们奖品是什么,然后我们在根据返回做前端id的转换,这样就能完成一个较为完善的闭环。
总结
本次记录的是个这两天做的小需求,也算是我首次尝试做类似的东西,相信在不远的将来会有一个大转盘等着我(微笑)。
,一个用create-react-app临时搭的demo,希望会有帮助,谢谢。