react实现转盘抽奖功能

看这个文章不错,借鉴 这个博主 的内容

样式是背景图片直接,没有设置。需要的话应该是

#bg {

width: 650px;

height: 600px;

margin: 0 auto;

background: url(turntable-bg.jpg) no-repeat;

position: relative;

}

    img[src^="pointer"] {
        position: absolute;
        z-index: 10;
        top: 155px;
        left: 247px;
    }

    img[src^="turntable"] {
        position: absolute;
        z-index: 5;
        top: 60px;
        left: 116px;
        transition: all 4s;
    }

自己在稍微改改啊

实现原理: 通过css3的 transition 和 transform 两个属性

1 首先,我们简单定义一个奖品数组,实际开发中是调后台接口获取奖品,以下是为了方便演示

const giftArr = [
{giftName: 'iphone xs'},
{giftName: '小米智能音箱'},
{giftName: 'ThinkPad X390 LTE版'},
{giftName: 'air pods 2'},
{giftName: '雷蛇鼠标'}]

2、如下图划分区域,因为转盘旋转时是顺时针旋转,所以按照下图划分奖品区域,图中序号表示奖品数组每一项的index。

3、定义每个奖品的角度区域

const LOTTERY_AREA_DEG = [[1, 59], [61, 119], [121, 179], [181, 239], [241, 299], [301, 359]]

这里我们把60度的整数倍度数给去掉了,是为了防止转到60度,120度这样的度数

4、为了模拟抽中的奖品,我们写个方法随机生成奖品的序号,以及根据奖品序号拿到转盘需要转到的角度

// 生成两个数范围内的随机整数
const randomNum = (minNum, maxNum) => {
 return parseInt(Math.random() * (maxNum - minNum + 1) + 	minNum, 10);
}

const giftIndex = randomNum(0, 5)
// 随机取对应奖品区域中的一个角度
const targetDegree = randomNum(LOTTERY_AREA_DEG[giftIndex][0], 	LOTTERY_AREA_DEG[giftIndex][1])

5 逻辑思路:假设第一次抽中了奖品 "iphone xs" ,需要转到26°,第二次抽中了 "小米智能音箱",需要转到82°,这种情况其实很好理解,我们只需用 transform: rotate(26deg) 和 transform: rotate(82deg);

我们再来看另一种情况,假设第一次抽中了奖品 "air pods 2",对应区域时3,需要转到221°,第二次抽中了 "小米智能音箱" ,对应区域是1,需要转到70°,那这种情况下,继续用 transform: rotate(70deg) 肯定是不行了,如果这样,会出现转盘逆时针转到区域1了,这样显然不是我们想要的结果,这种情况下我们就需要在70°的基础上再转360°,也就是转到430°的位置,才能达到顺时针旋转的效果。

接着上面的情况,第三次,我们抽中了 "iphone xs",对应区域是0,对应角度40°,我们按照上面的方法,40°比430°小,不能直接使用transform,那我们就给40°加360°,加了一个360°还不够430°,我们再加,加到2个360°后,发现360 * 2 + 40 = 760 > 430,可以,第三次转到730°就可以。

从上面我们可以发现,下一次需要旋转到的角度一定要比上一次的度数要大, 注意:这里的角度是旋转到多少度,而不是旋转了多少度,这是两个不同的概念,笔者之前就是按照旋转了多少度来计算的,结果除了第一次能旋转到对应的区域,后面每一次都不会旋转到对应的区域了

//  核心代码
let rotateDeg = 0
// 递归计算下次要转到的度数
let i = 0
const _fn = (n = 0) => {
    if (targetDegree + 360 * n > this.state.startRotateDeg) {
        rotateDeg = targetDegree + 360 * n
    } else {
        i++
        _fn(i)
    }
}
_fn()

完整代码

import React from 'react'
export default class Lottery extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            startRotateDeg: 0 // 记录上一次转到的角度
        }
    }

    randomNum = (minNum, maxNum) => {
        return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
    }

    handleClick = () => {
    	const LOTTERY_AREA_DEG = [[1, 59], [61, 119], [121, 179], [181, 239], [241, 299], [301, 359]]
        const giftIndex = this.randomNum(0, 5)
        // 随机取对应奖品区域中的一个角度
        const targetDegree = this.randomNum(LOTTERY_AREA_DEG[giftIndex][0], LOTTERY_AREA_DEG[giftIndex][1])
        let rotateDeg = 0
        // 递归计算下次要转到的度数
        let i = 0
        const _fn = (n = 0) => {
            if (targetDegree + 360 * n > this.state.startRotateDeg) {
                rotateDeg = targetDegree + 360 * n
            } else {
                i++
                _fn(i)
            }
        }
        _fn()
        // 获取转盘实例
        const ele = document.getElementById('turntable')
        // 增加旋转动画
        ele.style.transition = 'all 6500ms'
        ele.style.transform = `rotate(${rotateDeg + 360 * 10}deg)` // 乘以10是为了转盘转动的效果

        this.setState({
            startRotateDeg: rotateDeg + 360 * 10   // 记录上一次旋转到的角度
        })
    }

    render(){
        return(
            <div>
                {/* 转盘 */}
                <div className="turntable" id="turntable"></div>
                {/* 指针 */}
                <div className="pointer" onClick={this.handleClick}></div>
            </div>
        )
    }
}

这个就结束了。

在看看这个写的转盘活动抽奖。


1.实现旋转

原理很简单,就是通过css动画来实现旋转动画,用js来控制旋转度,

    img[src^="turntable"] {
        position: absolute;
        z-index: 5;
        top: 60px;
        left: 116px;
        transition: all 4s;
    }
//配合
oTurntable.style.transform = "rotate(" +transformRotate+ "deg)";

2.控制得奖概率

控制概率,我将100当做概率,概率为 [randmArr[i-1],randmArr[i]),通过控制差值,来控制概率,当然这只是简单的demo,最好用map对象,更合理。

  var randmArr = [1,5,10,20,35,55,100] //概率计算为randmArr[i-1]和randmArr[i]之间
   const rdm =parseInt(Math.random() * (100 - 1) + 1);//随机的数
   var num = 7
   console.log('rdm='+rdm);
   // 统计随机数次数
   if (!obj[rdm]) {
         obj[rdm] = 1;
       } else {
         obj[rdm] ++;
       }
   for(var i = 0;i<randmArr.length;i++){
     if(i===0){
         if(rdm>=0&&rdm<=randmArr[0]){
           num = i+1
           break
         }
     } else {
       if(rdm>randmArr[i-1]&&rdm<=randmArr[i]){
         num = i+1
           break
         }
     }
 }
  1. 控制旋转动画落点

    虽然js是控制,单动画落点,也需要和我们所得一致,同时我们思考,一般抽奖都会固定旋转几圈,所以我们需要给个初始的旋转圈数,同时下一次旋转又是从上一次落点的基础上进行的,为了控制我们每次都是从原点开始,这样才能控制好落点和js一致,于是我设置初始圈为3圈,而第三圈为,第一次旋转后剩下度数(360-n),这样就能达到每次都是从原点开始。

     	nextrdm = Math.floor((num* cat)-24); //定义本次抽奖结果
        var biginRotate = 2*360+(360-prevrdm) //定义默认的旋转圈数,同时补全使轮盘置零,
        prevrdm = nextrdm //缓存本次次的角度
           transformRotate=nextrdm+biginRotate+transformRotate //本次旋转的度
        oTurntable.style.transform = "rotate(" +transformRotate+ "deg)";
    

4.测试概率

写是写完了,但不测试都是瞎扯淡,所以我定义了2个对象,用了记录每次所得的随机数和次数,还有中几等奖的次数

obj 和 form对象

const rdm =parseInt(Math.random() * (100 - 1) + 1);//随机的数 
// 统计随机数次数 
if (!obj[rdm]) {
    obj[rdm] = 1;
} else {
    obj[rdm] ++;
}

  // 统计中奖概率
if ( !form['a'+num]) {
    form['a'+num] = 1;
} else {
    form['a'+num]++
}

打印发现其实随机比较公平的,但是毕竟用的100,中奖概率还是比较大了,如果需要再小可以继续放小,

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="description" content="">
    <meta name="author" content="marendu">
    <title>转盘抽奖效果</title>
    <!-- 这里是css部分 -->
    <style>
        #bg {
            width: 650px;
            height: 600px;
            margin: 0 auto;
            background: url(turntable-bg.jpg) no-repeat;
            position: relative;
        }

        img[src^="pointer"] {
            position: absolute;
            z-index: 10;
            top: 155px;
            left: 247px;
        }

        img[src^="turntable"] {
            position: absolute;
            z-index: 5;
            top: 60px;
            left: 116px;
            transition: all 4s;
        }
    </style>
</head>
<body>
    <!-- 这里是HTML结构部分 -->
    <div id="bg">
    	<img src="pointer.png" alt="pointer">
    	<img src="turntable.png" alt="turntable">
    </div>
    <button onclick="btn()">概率统计</button>
    <button onclick="test()">测试100次抽奖</button>
    <!-- 这里是js部分 -->
    <script>
        var oPointer = document.getElementsByTagName("img")[0];
        var oTurntable = document.getElementsByTagName("img")[1];
        var cat = 51.4; //总共7个扇形区域,每个区域约51.4度
        var offOn = true; //是否正在抽奖
        var transformRotate = 0 //转圈度数
        var randmArr = [1,5,10,20,35,55,100] //概率计算为randmArr[i-1]和randmArr[i]之间
        // 打印概率
        function btn(){
            console.log(form);
            console.log(obj);
            let num = 0
          Object.keys(obj).map(item=>{
            num += obj[item]
          })
          console.log(num);
        }
        oPointer.onclick = function () {
            if (offOn) {
                offOn = !offOn;
                ratating();
            }
        }
          let n= 0
          var testTime = null
          function test(){
            testTime = setInterval(()=>{
                n++
                if(n>99){
                clearInterval(testTime)
             }
               ratating();
           },300);
      }
        //旋转
        //TODO: 规定中奖区间,通过区间判定值
        var prevrdm = 0; //缓存上一次的旋转度
        var obj ={} //产生的随机数的次数
        var form = {} //统计中奖的概率
        var timer = null
        function ratating() {
            var timer = null;
            const rdm =parseInt(Math.random() * (100 - 1) + 1);//随机的数
            var num = 7
            var nextrdm = 0; //本次旋转度
            console.log('rdm='+rdm);
            // 统计随机数次数
            if (!obj[rdm]) {
                  obj[rdm] = 1;
                } else {
                  obj[rdm] ++;
                }
            for(var i = 0;i<randmArr.length;i++){
              if(i===0){
                  if(rdm>=0&&rdm<=randmArr[0]){
                    num = i+1
                    break
                  }
              } else {
                if(rdm>randmArr[i-1]&&rdm<=randmArr[i]){
                  num = i+1
                    break
                  }
              }
          }
          console.log('num='+num);
            clearTimeout(timer);
            timer = null
            timer = setTimeout(function () {
              nextrdm = Math.floor((num* cat)-24); //定义本次抽奖结果
              var biginRotate = 2*360+(360-prevrdm) //定义默认的旋转圈数,同时补全使轮盘置零,
              prevrdm = nextrdm
              transformRotate=nextrdm+biginRotate+transformRotate // 本次旋转的度
              // 测试中奖的概率时,把下面注释掉,同时将timeout时间设置为0
              oTurntable.style.transform = "rotate(" +transformRotate+ "deg)";
                    setTimeout(function () {
                        offOn = !offOn;
                        // 统计中奖概率
                          if ( !form['a'+num]) {
                            form['a'+num] = 1;
                          } else {
                            form['a'+num]++
                          }
                        console.log('transformRotate='+transformRotate);
                        if (nextrdm <= cat * 1) { 
                        	console.warn("一等奖"); 
                        	console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "4999元");
                        }
                        else if (nextrdm <= cat * 2) {
	                        console.warn("二等奖"); 
	                        console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "50元");
	                    }
                        else if (nextrdm <= cat * 3) {
                            console.warn("三等奖");
                            console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "10元");
                        }
                        else if (nextrdm <= cat * 4) {
                            console.warn("四等奖");
                            console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "5元");
                        }
                        else if (nextrdm <= cat * 5) {
                            console.warn("五等奖");
                            console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "免息服务");
                        }
                        else if (nextrdm <= cat * 6) {
                            console.warn("六等奖");
                            console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "提交白金");
                        }
                        else if (nextrdm <= cat * 7) { 
                             console.warn("七等奖");
                             console.log("nextrdm=" + nextrdm + "rdm=" + rdm + "," + "未中奖");
                       }
                  }, 0);
            }, 30);
        }
    </script>
</body>
</html>
相关推荐
J不A秃V头A22 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
光影少年34 分钟前
usemeno和usecallback区别及使用场景
react.js
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
哆木2 小时前
部署在线GBA游戏,并通过docker安装启动
游戏·html·gba
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js