跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

【JavaScript】制作一个抽奖转盘页面

开发H5项目,有时会遇到一个需求,需要制作抽奖转盘的网页,这个实现步骤,如果拿现成的改来做是容易的,但是想着全靠自己做是不容易的,下面会讲,全靠自己做,能掌握到吗 目录一览 1.设计网页 2. 编写脚本 3. 编写模块 4. 实现方法 1. 绘制转盘 2. 开始抽奖 4.运行效果 1.设计网页 首先创建一个网页文件,例如

index.html

,制作抽奖转盘页面,源代码如下,通过修改样式

2. 编写脚本 接着,写一个加载脚本的处理逻辑,代码如下,使用

Turntable

对象创建前,需要先引用一个模块

const t = new Turntable({

window, elemId: 'box', }); //TODO: 最多7个 const resouce = [ { image: './img/e7b2e38c8d66613d1dd869b199031d8e.jpeg', title: '特等奖' }, { image: './img/b5ff4601d9679f502f8f9e737bdd7049.jpeg', title: '谢谢惠顾' }, { image: './img/e7b2e38c8d66613d1dd869b199031d8e.jpeg', title: '一等奖' }, { image: './img/b5ff4601d9679f502f8f9e737bdd7049.jpeg', title: '谢谢惠顾' }, { image: './img/e7b2e38c8d66613d1dd869b199031d8e.jpeg', title: '二等奖' }, { image: './img/e7b2e38c8d66613d1dd869b199031d8e.jpeg', title: '三等奖' }, { image: './img/b5ff4601d9679f502f8f9e737bdd7049.jpeg', title: '谢谢惠顾' }, ]; //加载图片资源可能有延迟,通过异步处理 Promise.all(resouce.map((item)=>{ return new Promise((resolve,reject)=>{ let img = new Image(); img.onload = () => { item.image = img; resolve(item); }; img.onerror = reject; img.src = item.image; }); })).then((res)=>{ t.draw({ // mode:1, goods:res, }); }); //设置按钮点击事件 document.getElementById('btnStart').onclick = () => { t.onStart({ // index: 3,//抽奖概率自己写,传入预定奖品的index success:(res)=>{ // console.log('ok', res); const good = res.goods[res.index]; if (good.title) { if (good.title.indexOf('奖')>=0) alert(`🙂恭喜恭喜您!抽到奖品${good.title}.`) else alert('🙆‍很遗憾!未中奖.') }else { alert(`🙂恭喜恭喜您!抽到奖品${res.index+1}.`) } } }); }

3. 编写模块 接下来,看上面有引用的一个模块文件

turntable.js

,没有的就把它新建好,在一个模块中去实现上面未实现的调用方法,代码如下

export default class Turntable {

//定义私有属性 #elemBgImg; #elemPointerImg;

#bgImgCanvas; #pointerImgCanvas;

/** * 构造函数 * */ constructor(e){ const { document } = e.window; // 获取占位元素(盒子) const elemBox = document.getElementById(e.elemId); // 创建元素 const elemBgImg = document.createElement('img'); const elemPointerImg = document.createElement('img'); const size = elemBox.offsetWidth; // 设置元素样式 elemBox.style.display = 'inline-block'; elemBox.style.position = 'relative'; elemBgImg.style.transform = `rotate(0deg)`; elemBgImg.style.pointerEvents = 'none';//屏蔽触摸点击 elemPointerImg.style.pointerEvents = 'none'; elemBgImg.style.position = 'absolute'; elemPointerImg.style.position = 'absolute'; elemPointerImg.style.margin = 'auto'; elemBgImg.style.margin = 'auto'; elemBgImg.style.left = 0; elemBgImg.style.top = 0; elemBgImg.style.right = 0; elemBgImg.style.bottom = 0; elemPointerImg.style.left = 0; elemPointerImg.style.top = 0; elemPointerImg.style.right = 0; elemPointerImg.style.bottom = 0; elemBgImg.width = size; elemBgImg.height = size; elemPointerImg.width = size*0.3; elemPointerImg.height = size*0.3; //将元素添加到占位元素(盒子)组件中 elemBox.appendChild(elemBgImg); elemBox.appendChild(elemPointerImg); this.#elemBgImg = elemBgImg; this.#elemPointerImg = elemPointerImg; //转盘 const bgImgCanvas = document.createElement('canvas'); bgImgCanvas.width = size; bgImgCanvas.height = size; this.#bgImgCanvas = bgImgCanvas; //指针 const pointerImgCanvas = document.createElement('canvas'); pointerImgCanvas.width = elemPointerImg.width; pointerImgCanvas.height = elemPointerImg.height; this.#pointerImgCanvas = pointerImgCanvas; } /** * 销毁 * */ destory(){ this.#bgImgCanvas.remove(); this.#pointerImgCanvas.remove(); } /** * 绘制转盘组件 * */ draw(config){ //... }

/** * 开始抽奖 * */ onStart(config){ //... } }

4. 实现方法 接下来,写方法的实现细节要复杂得多,如果看着比较吃力,就需要补充数学知识哦,关键点是三角图形学中的勾股定理,请慢慢摸索,边学边做 1. 绘制转盘 先实现绘制转盘组件方法

draw()

,其中用到了数学的一个知识点: 三角函数 ,代码如下

class Turntable {

//定义私有属性 #goods=[]; #pointerDeg = 0; #mode = 0; //...

/** * 绘制转盘组件 * */ draw(config){ const data = { padding: 5,//组件内边距 goods: ['#f00','#0f0','#00f'],//默认三基色填充礼品区 pointerColor: '#fa0',//指针色 borderWidth: 10,//边框大小 borderColor: '#fa0',//边框色 imgSize: 40,//礼品图片大小 mode:0,//工作模式: 0:转动转盘;1:转动指针 }; Object.assign(data,config); this.#mode = data.mode==0 ? 0 : 1; const bgImgCanvas = this.#bgImgCanvas; const bgImgCtx = bgImgCanvas.getContext('2d'); const coodrinte = { padding: data.padding, r: bgImgCanvas.width/2-data.padding }; coodrinte.centerO = coodrinte.padding + coodrinte.r; //先绘制转盘底座 bgImgCtx.strokeStyle = data.borderColor; bgImgCtx.lineWidth = data.borderWidth; bgImgCtx.fillStyle = '#eee'; bgImgCtx.beginPath(); bgImgCtx.arc(coodrinte.centerO,coodrinte.centerO,coodrinte.r,0,Math.PI*2); bgImgCtx.fill(); bgImgCtx.stroke(); //再绘制转盘上的 bgImgCtx.strokeStyle = 'rgba(255,255,255,0.3)'; bgImgCtx.lineWidth = Math.max(1,data.borderWidth/3); bgImgCtx.textAlign = 'center'; const r = coodrinte.r-bgImgCtx.lineWidth; //转盘角度(弧边) let startAngle = 0; let endAngle = 0; data.goods.forEach((item,index)=>{ let good = { proportion: Math.round(1000/data.goods.length)/1000,//默认平分概率 }; switch(typeof item){ case 'string': if (item.charAt(0)=='#') good.bgColor=item; else good.title=item; break; case 'object': Object.assign(good,item); break; default: throw new Error('定义参数goods有误'); } good.startAngle = startAngle; good.endAngle = good.startAngle+Math.PI*2*good.proportion; //计算角度 let angle = (good.endAngle-good.startAngle)/2-Math.PI*0.5+(index*good.proportion*Math.PI*2); //余弦函数cosA:表示在一个直角三角形中,∠A(非直角)的邻边与三角形的斜边的比 let x = Math.cos(angle)*(r/2); //正弦函数sinA:表示在一个直角三角形中,∠A(非直角)的对边与三角形的斜边的比 let y = Math.sin(angle)*(r/2); // console.log('angle '+angle, 'x='+x+',y='+y); good.center = { x:coodrinte.centerO+x, y:coodrinte.centerO+y, }; data.goods[index] = good; startAngle = good.endAngle; }); //绘制分布在转盘中的图案 data.goods.forEach((item,index)=>{ if (item.bgColor){ bgImgCtx.fillStyle = item.bgColor; } //画划分的区域(弧边) bgImgCtx.beginPath(); bgImgCtx.moveTo(coodrinte.centerO,coodrinte.centerO); bgImgCtx.arc(coodrinte.centerO,coodrinte.centerO,r,item.startAngle-Math.PI*0.5,item.endAngle-Math.PI*0.5); bgImgCtx.closePath(); if (!item.bgColor) { bgImgCtx.stroke(); bgImgCtx.fillStyle = '#f50'; }else{ } bgImgCtx.fill(); //是否是转动底盘 if (this.#mode==0) { bgImgCtx.save(); let cX = item.center.x; let cY = item.center.y; let angle = Math.round(Math.atan(Math.abs(coodrinte.centerO-cY)/Math.abs(coodrinte.centerO-cX))*180/Math.PI); // console.log(index+'. angle > '+angle) //TODO: 暂时适配最多7个 switch(angle){ case 0: if (cX

}else if (angle<=60) { angle-=30; }else { angle+=10; } }else{ if (cY

}); this.#goods = data.goods; this.#elemBgImg.src = bgImgCanvas.toDataURL(); //绘制指针 const pointerImgCanvas = this.#pointerImgCanvas; const pointerImgCtx = pointerImgCanvas.getContext('2d');

const pointerData = { r: pointerImgCanvas.width/2 }; pointerData.r1 = pointerData.r*0.36; pointerData.r2 = pointerData.r*0.60;

pointerImgCtx.fillStyle = data.pointerColor; startAngle = Math.PI*1.58; endAngle = startAngle + Math.PI*1.86; pointerImgCtx.lineWidth = 2; pointerImgCtx.beginPath(); pointerImgCtx.arc(pointerData.r,pointerData.r,pointerData.r2,startAngle,endAngle); pointerImgCtx.lineTo(pointerData.r,0); pointerImgCtx.closePath(); pointerImgCtx.fill(); pointerImgCtx.stroke(); //将绘制的图形设置到图片元素 this.#elemPointerImg.src = pointerImgCanvas.toDataURL(); } }

2. 开始抽奖 实现开始抽奖方法

onStart()

,可通过传入参数对象

config

,修改默认配置,代码如下,抽奖结果会通过回调方法

succes()

返回

class Turntable {

//定义私有属性 #animing = false; //...

/** * 开始抽奖 * */ onStart(config){ if (this.#animing) return;//防止双击(误操作) this.#animing = true; const data = { minRotationNum: 3,//至少转动圈数 duration:3,//转动耗时3s success(res){},//结束时回调 index: -1,//抽得预定奖品,默认随机 }; Object.assign(data,config); const goods = this.#goods; if (data.index<0 || data.index>=goods.length) { //抽得随机奖品 data.index = Math.trunc(Math.random()*10%goods.length); } const elemActive = this.#mode==0 ? this.#elemBgImg : this.#elemPointerImg; const style = elemActive.style; const pointerDeg = this.#pointerDeg; const index = data.index; //定义动画结束监听 const listener = (event) => { event.preventDefault(); elemActive.removeEventListener('transitionend', listener); //重置动画样式 style.transition = 'none'; style.transform = `rotate(${this.#pointerDeg}deg)`; //结束和回调 this.#animing = false; data.success({ goods, index }); }; elemActive.addEventListener('transitionend', listener, false); //处理过渡动画 let inDeg = Math.round(goods[index].startAngle/Math.PI*180); let outDeg = Math.round(goods[index].endAngle/Math.PI*180); let deg = (Math.round(Math.random()*(outDeg-inDeg)))+inDeg; // console.log('rand '+index, `${inDeg}°~${outDeg}° ${deg}° current:${pointerDeg}`); //转盘是反向旋转的 if (this.#mode==0) deg = 360-deg; deg += (Math.round(Math.random()*10)+data.minRotationNum)*360; //简化角度 this.#pointerDeg = deg%360; //修改完样式,就可开始动画 style.transition = `all ${data.duration}s ease-out`; style.transform = `rotate(${deg}deg)`; } }

4.运行效果 讲到最后,用浏览器打开网页

index.html

浏览看看,正常的话,运行效果图如下

💡小提示 试试修改传入的参数,例如

t.draw({

//... mode:1,//改变为指针转动 /... }); //... t.onStart({ index: 3,//抽奖概率自己写,传入预定奖品的index success:(res)=>{ //... } });

可根据其它的需求改 到此结束,如阅读中有遇到什么问题,请在文章结尾评论处留言,ヾ( ̄▽ ̄)Bye Bye

相关文章