最近公司要做一个转盘抽奖的效果,但是我们可以控制转盘抽奖的概率,自己用es6简单的实现了下,中间虽然遇到一些问题,但最终都是解决了,下面说一下我的思路。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="stylesheet" href="css/index.css"> <script src="js/index.js"></script> </head> <body> <div class="main"> <div class="active"> <div class="rotate_title"><img src="images/rotate_title.png" alt=""></div> <div class="rotate"> <div class="zd_round"><img src="images/zd.png" alt=""></div> <div class="pointer"><img src="images/pointer.png" alt="" width="100%"></div> </div> </div> </div> <script> new Draw({ el: ".zd_round img",//转动的元素 btn:".pointer", //点击转动的元素 count: 3, //转盘转动的圈数, chance: { 1: "40%", 5: "40%", //指定抽奖概率,其余则平分 }, num:8,//转盘的总个数 innerTime:4000, //转盘转动的时间 endFunc:function(pos){ console.log(pos) } //转盘动画结束后的回调函数 }) </script> </body> </html>
上面是html代码,我就不多做介绍了,效果原理主要还是利用css3 的rotate属性实现元素的旋转,下面我们看js代码,我们使用的事面向对象编程,我们可以在前台配置参数多次调用 。下面我们开始js代码的编写:
(function () { function $_(name) { return document.querySelector(name); } class Draw { constructor(opt) { } } window.Draw=Draw; })();
首先我们通过一个匿名函数自执行的方式,这样做的好处的是防止内部的变量污染全局,里面的变量都形成了一个局部变量,最下面我们通过把Draw类暴露给全局,这样我们就可以在外部直接调用。
constructor(opt) { let defaultopt = { el: ".zd_round img",//转动的元素 btn: ".pointer", //点击转动的元素 count: 3, //转盘转动的圈数, chance: { 1: "40%", 5: "40%", //指定抽奖概率,其余则平分 }, num: 8,//转盘的总个数 innerTime: 4000 //转盘转动的时间 } opt = Object.assign({}, defaultopt, opt); this.init(opt); }
我们需要知道的是这是一个构造函数,当我们new 一个类的时候会执行构造函数,把this绑定的属性方法添加到这个类上面,可以看到我们做的事把我们外部调用的参数和我们内部调用的参数的一个合并,我们用到的是es6的Object.assign的方法,不同的童鞋请自行百度,最后我们得到了合并后的一个对象。
init(opt) { let { el, chance, count, num, btn, innerTime ,endFunc} = opt; let numArray = Object.keys(chance); this.residue = []; for (let i = 0; i < num; i++) { if (!numArray.includes(String(i))) { this.residue.push(i) } } this.pos = this.getNum(chance); //确定转盘最后停留的位置 this.deg = 360 / num; this.rotate(el, count, btn, innerTime,endFunc); }
init函数主要做一些初始化操作,通过es6解构赋值保存传进来的参数,主要难点怎么控制概率,我下面定义了一个方法getNum来获取最后停留的位置,我定义了一个变量得到0-100的随机数,通过传进来的概率值来判断,这个不好描述,通过举例说明:
var n1 = Math.round(Math.random() * 100); //获取100之内的任意一个整数; if (n1 < 95) { console.log(我是几率为95 % 的) } else { console.log(我是几率为5 % 的) }
有了这个思路就简单多了,我们可以通过传进来的概率对象进行遍历来判断,如果在概率范围内直接return出这个对象的索引,也就是概率的选项,那么就会有一个问题,就是如果配置的选项都没有抽中,我们该做什么处理,init函数里面我们定义了一个数组,保存了配置选项之外的选项数组,如果没有抽中配置的选项,我们从剩下的选项中随机抽中一个。
getNum(chance) { let n1 = Math.round(Math.random() * 100); let start = 0, end = 0; for (let key in chance) { end = start + parseInt(chance[key]); if (n1 > start && n1 < end) { return key; } start = end; } //如果配置的概率没有抽中,则剩下的商品概率评分 let rad = Math.floor(Math.random() * (this.residue.length)); return this.residue[rad]; }
接下来就比较简单的,我们就可以操作转动的元素,以及给这个元素设置动画,这里我们用的是css3 过渡属性,所以实现起来比较简单,也不需要写定时器了。
rotate(el, count, btn, innerTime, endFunc) { var endPos = (count * 360 - this.deg / 2) + (this.pos * this.deg) + Math.random() * this.deg / 2 + 10; var btn = $_(btn); var el = $_(el); btn.onclick = () => { this.aniamte(el, endPos,innerTime); } el.addEventListener("transitionend", this.end.bind(this, endFunc), false);
} aniamte(el, endPos, innerTime) { el.style.transform = "rotate(" + endPos + "deg)"; el.style.transition = "all " + innerTime + "ms linear"; }
end(endFunc) { endFunc && endFunc(this.pos); }
最后我们在动画结束后可以传入一个回调函数,这样我们用的时候就不用改内部的代码,在调用插件的时候就可以做一些操作。最后附上全部的代码,方便大家理解:
(function () { function $_(name) { return document.querySelector(name); } class Draw { constructor(opt) { let defaultopt = { el: ".zd_round img",//转动的元素 btn: ".pointer", //点击转动的元素 count: 3, //转盘转动的圈数, chance: { 1: "40%", 5: "40%", //指定抽奖概率,其余则平分 }, num: 8,//转盘的总个数 innerTime: 4000 //转盘转动的时间 } opt = Object.assign({}, defaultopt, opt); this.init(opt); } init(opt) { let { el, chance, count, num, btn, innerTime ,endFunc} = opt; let numArray = Object.keys(chance); this.residue = []; for (let i = 0; i < num; i++) { if (!numArray.includes(String(i))) { this.residue.push(i) } } this.pos = this.getNum(chance); //确定转盘最后停留的位置 this.deg = 360 / num; this.rotate(el, count, btn, innerTime,endFunc); } getNum(chance) { let n1 = Math.round(Math.random() * 100); let start = 0, end = 0; for (let key in chance) { end = start + parseInt(chance[key]); if (n1 > start && n1 < end) { return key; } start = end; } //如果配置的概率没有抽中,则剩下的商品概率评分 let rad = Math.floor(Math.random() * (this.residue.length)); return this.residue[rad]; } rotate(el, count, btn, innerTime,endFunc) { var endPos = (count * 360 - this.deg / 2) + (this.pos * this.deg) + Math.random() * this.deg / 2 + 10; var btn = $_(btn); var el = $_(el); btn.onclick = () => { this.aniamte(el, endPos,innerTime); } el.addEventListener("transitionend", this.end.bind(this,endFunc), false); } aniamte(el, endPos, innerTime) { el.style.transform = "rotate(" + endPos + "deg)"; el.style.transition = "all " + innerTime + "ms linear"; } end(endFunc) { endFunc&&endFunc(this.pos); } } window.Draw=Draw; })();
最后说一个坑,可能之前数学学多了,我们知道数学里面可以写一个变量大于小于某个数值,比如3<n<5,这个表示的是大于3小于5的一个数字,然后我在if判断也这样写,发现返回都是true.
我觉得还是老老实实的用&吧。
n1 > start && n1 < end
代码可以去我的github下载 https://github.com/shentaochok/Draw.git
寄语:怕,你就会输一辈子!