最近有一个需求要开发一个抽奖大转盘类的一个页面,本来想着这种东西肯定有现成的,但找了一圈后发现基本上都与我们的设计图有些出入,如果直接使用现成的包估计肯定是验收不通过的
开源项目的布局一般都是这样的(转盘内的文字正对圆心),推荐一个比较热门的开源项目,支持多端,多种游戏方式(大转盘、九宫格、老虎机)
而我们的设计图却是设计成这样的(文字与半径平行)
无奈现成的包的不能使用,故准备把开源项目 down 下来修改一下使之符合我们的业务场景要求
下面总结了一些实现思路:分为 canvas 版本和 div+css 版本
canvas 思路实现
- canvas 转盘绘制,包括每片扇区上的文字及图片的绘制(其实就这一点需要更改)
- 递归 requestAnimationFrame 然后不断改变绘制起始角度(canvas绘制的初始角度)使转盘动起来
- 使用缓动算法,使转盘转动更加自然:转盘初始阶段缓慢
加速 -> 匀速 -> 减速
最终停在指定扇区(中奖结果肯定是 api 返回的,前端需要控制最后要停在指定位置上,按照 lucky-canvas 上的说法就是刻舟求剑
)
下面分别说一下注意点:
canvas 绘制需要注意,起始角度是从 三点钟开始的,绘制一圈是
2 * Math.PI
,为了让起始位置落在第一个扇区上需要逆时针旋转一定的角度(按我的需求是有8个扇区,也就是需要旋转2 * Math.PI / 8 * 2
),开发时建议将弧度(不知道此处用弧度表示对不对)转换成角度,这样运算过程更加便捷,也会减少无限不循环小数产生的误差/** * 转换为运算角度 * @param { number } deg 数学角度 * @return { number } canvas 画圆角度 */ const getAngle(deg: number): number => (Math.PI / 180) * deg
- requestAnimationFrame 没什么好说的,现在浏览兼容性也不是什么问题了
- 缓动算法:其实就是根据一些列变量,算出一个值(动画的变化量),具体参考张鑫旭大大的tween.js的简单实现
- 转盘停止停在的扇区,根据 api 返回的中奖结果得出扇区索引 index 即:
const prizeDeg = 360 - index * (360 / 8) + initDeg;
(initDeg即最开始旋转的初始角度)
在需求完成之后,我发现不用 canvas,用 div 也能很方便的实现这种效果
div 实现
除了第一步转盘绘制,其他两步思路大致相同
- 用 div + css 代替 canvas ,主要用到了
transform: rotate(x) skew(y)
,需要注意的一点是,旋转扭曲之后 div 的子元素也会跟着旋转扭曲,我本来以为这样写就行了transform: rotate(-x) skew(-y)
,但殊不知 transform 变换是有顺序的要这样写transform: skew(-y) rotate(-x)
参考 - 用了 div 之后,旋转就不是不断地重绘 canvas 了,只需要不断地改变父集元素的
transform: rotate()
,相比 canvas 要简单了许多
代码参考 https://github.com/hugeorange...
- 核心代码采用 ts 开发(不依赖框架)
- 考虑到我的这个代码满足的场景比较小众并没有封装成 npm 包
- 有同样场景需求的朋友可以直接拷贝代码