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,randmArri),通过控制差值,来控制概率,当然这只是简单的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>
相关推荐
AlienZHOU6 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
liangshanbo12156 小时前
面试题:React 中 useReducer 和 useState 有什么区别?什么时候应该使用 useReducer?
react·usereducer
刺客码8 小时前
Layui 表格固定列行高错位问题解决方案
前端框架·layui·jquery·web
Captaincc9 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师10 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen11 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒11 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow11 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81811 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007811 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端