转盘的简单实现

通过代码实现抽奖的转盘,使用html、css、js实现(并未使用canvas)。参考例子:JS写的一个抽奖小Demo从普通写法到设计模式再向ES6的进阶路程

实现

样式实现

ul是转盘部分,将转盘划分成12份。pointer则是点击转动部分。主要的html结构如下:

<ul class="turntable">
    <li><span>1</span></li>
    <li style="transform: rotateZ(30deg);"><span>2</span></li>
    <li style="transform: rotateZ(60deg);"><span>3</span></li>
    <li style="transform: rotateZ(90deg);"><span>4</span></li>
    <li style="transform: rotateZ(120deg);"><span>5</span></li>
    <li style="transform: rotateZ(150deg);"><span>6</span></li>
    <li style="transform: rotateZ(180deg);"><span>7</span></li>
    <li style="transform: rotateZ(210deg);"><span>8</span></li>
    <li style="transform: rotateZ(240deg);"><span>9</span></li>
    <li style="transform: rotateZ(270deg);"><span>10</span></li>
    <li style="transform: rotateZ(300deg);"><span>11</span></li>
    <li style="transform: rotateZ(330deg);"><span>12</span></li>
</ul>
<div class="pointer" >
    <p onclick="startRotate()">开始抽奖</p>
</div>    

实现的效果如图:


ul相对定位后,li设为绝对定位。li样式使用border做成三角形,父元素设置overflow: hidden。然后再对每个li进行rotate变换就可以实现如图的形状。再将开始按钮放置到中间盖住中心部分,就实现如图的整体效果。

转盘转动实现

其实在点击的时候,就已经确定好指针要指向的部分。转盘分为12份,则随机一个1-12的数,每一部分在圆中所占据的角度是30°。例如:第一部分(1)占据了345-15°,则12占据了16-45°…以此类推。
因为要制作一个转动的过程,所以需要再随机一个数,这个数就代表要转的圈数,圈数乘上360,加上选中的数所在的度数,即

转盘转动的总度数 = 圈数 * 360° + 度数

但由于每一部分占据的是一个范围,所以指针最后所在的区间范围(以12为例):

max度数 = 圈数 360° + 45°
min度数 = 圈数
360° + 16°

这里肯可能有疑问:12在圆中为什么是16-45°,而不是316-345°。原因是:转盘是按顺时针方向进行转动的,所以当转盘转动时,1过后指针指向的是12、11、10、9…。所以在计算对应位置时,是按1、12、11…的顺序进行的。同时由于1占据345-15°这样一个范围,为了方便计算,我们将其范围设为-16-15°。同时,为了避免指针在转动结束时指向两个部分的交界处,显示不明显,所以我们在左右各设置一个缓冲区,让指针最后不会停留在缓冲区。如图:

指针最后只会落在红线之间。

为了转盘在开始转动和结束时带有速度递增与递减的效果,需要设置一个加速度、减速度,还要计算出在减速过程中所需要转过的度数,得到开始减速的度数

min度数 - 减速过程中转过的度数 = 开始减速的度数

数据预处理的逻辑如下:
>
function prepareData(number){
// 默认12格
number ? ‘’ : number = 12;
let everyDeg = 360 / number,
bufferDeg = 8, // 左右各缓冲4deg
numbersDeg = [];
// 每格所占的度数
let initCount = (everyDeg-bufferDeg)/2;

    numbersDeg[1] = [-initCount , initCount];
    for(let i=number ; i >= 2 ; i--){
        numbersDeg[i+''] = [initCount+bufferDeg+1 , initCount+everyDeg];
        initCount += everyDeg;
    }

    let randomValue = Math.floor(Math.random()*number)+1, // 随机被选中的数
        rotateCount = Math.floor(Math.random()*3)+6, // 旋转6-8圈
        maxStep = Math.floor(Math.random()*3)+6, // 最大转速为6-8(deg)
        stepPlus = 0.05, // 转动的加速度
        stepLess = 0.02, // 转动的减速度
        step = 0, // 初始变化的角度
        initDeg = 0; // 初始转过角度

    let totalDeg = rotateCount * 360,
        maxDeg = totalDeg + numbersDeg[randomValue][1],
        minDeg = totalDeg + numbersDeg[randomValue][0],
        // 每次变化的角度在衰减过程中是等差数列,则在衰减过程中会转过的度数之和
        // 公式 ((startStep + maxStep) * n) / 2
        restDeg = (maxStep * (maxStep / stepLess)) /2;
    return {
        result: randomValue,
        maxDeg: maxDeg,
        minDeg: minDeg,
        maxStep: maxStep,
        step: 0,
        initDeg: 0,
        stepPlus: stepPlus, 
        stepLess: stepLess, 
        restDeg: restDeg,
        rotateState: 'stop' // 当前转盘状态['plus','less','stop'] 
    };
}    

开始抽奖,通过js代码改变转盘的rotate度数,就可以实现转盘的转动。不过还要考虑加速、减速、停止。

function rotate(){
    turntable.style.transform = "rotateZ("+data.initDeg+"deg)";
    // 计算下一次转盘的度数
    data.initDeg += data.step;
    // 加速
    if(data.step < data.maxStep && data.rotateState == 'plus'){
        data.step += data.stepPlus;
    }
    // 减速
    else if(data.initDeg >= data.minDeg - data.restDeg){
        data.step -= data.stepLess;
        data.rotateState = 'less';
    }

    if(data.initDeg >= data.minDeg && data.initDeg <= data.maxDeg && data.step < 0){
        alert(data.result);
        resetTurntable();
        console.log(`step: ${data.step}, deg: ${data.initDeg}`);
    }else{
        window.requestAnimationFrame(rotate);
    }
}

以上便是一个转盘实现的大致逻辑与代码。最后转动的效果如图: