首页IT科技设计一个转盘抽奖页面(【JavaScript】制作一个抽奖转盘页面)

设计一个转盘抽奖页面(【JavaScript】制作一个抽奖转盘页面)

时间2025-04-29 18:58:51分类IT科技浏览3178
导读:开发H5项目,有时会遇到一个需求,需要制作抽奖转盘的网页,这个实现步骤,如果拿现成的改来做是容易的,但是想着全靠自己做是不容易的,下面会讲,全靠自己做,能掌握到吗...

开发H5项目           ,有时会遇到一个需求               ,需要制作抽奖转盘的网页     ,这个实现步骤           ,如果拿现成的改来做是容易的               ,但是想着全靠自己做是不容易的     ,下面会讲      ,全靠自己做               ,能掌握到吗

1.设计网页

首先创建一个网页文件          ,例如index.html      ,制作抽奖转盘页面                ,源代码如下          ,通过修改样式<style>里设置好背景色,还有转盘组件的位置                ,再加一个抽奖按钮               ,写好大概逻辑,还有需要调用的一些方法

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Turntable 转盘</title> <style> body{ background-color: #333; } .box{ text-align: center; } .box #box{ width: 280px; height: 280px; } .box button{ margin-top: 20px; padding: 0.6em 2.5em; font-size: 1em; border-radius: 10px; border-color: rgba(0, 0, 0, 0.4); color: #fff; background: linear-gradient(#eee,#f50); } </style> </head> <body> <div class="box"> <div id="box"></div> <div> <button id="btnStart">抽奖</button> </div> </div> <script type="module"> import Turntable from ./turntable.js;//引用模块 window.onload = () => { //...加载脚本 } </script> </body> </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<coodrinte.centerO) angle+=90; else angle-=90; break; case 90: angle=0; break; default: if (cX<coodrinte.centerO){ if (cY<coodrinte.centerO){ angle+=90; }else if (angle<20) { angle+=45; }else if (angle<38) { angle-=25; }else if (angle<=40) { angle+=10; }else if (angle==45) { }else if (angle<=60) { angle-=30; }else { angle+=10; } }else{ if (cY<coodrinte.centerO){ angle=270-angle; }else{ angle-=90; } } } if (angle!=0){ //旋转角度               ,以转盘中心点对齐 angle=Math.PI*(angle/180); bgImgCtx.translate(cX,cY); bgImgCtx.rotate(angle); bgImgCtx.translate(-cX,-cY); } } if (item.image) { bgImgCtx.drawImage(item.image,item.center.x-data.imgSize*0.5,item.center.y-data.imgSize*0.5,data.imgSize,data.imgSize); } if (item.title) { bgImgCtx.fillStyle = #fff; bgImgCtx.fillText(item.title,item.center.x,item.image ? (item.center.y+data.imgSize*0.9) : item.center.y); } if (this.#mode==0){ bgImgCtx.restore(); } //画辅助线 // bgImgCtx.beginPath(); // bgImgCtx.moveTo(coodrinte.centerO,coodrinte.centerO); // bgImgCtx.lineTo(item.center.x,item.center.y); // bgImgCtx.stroke(); }); 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)=>{ //... } });

可根据其它的需求改

到此结束,如阅读中有遇到什么问题           ,请在文章结尾评论处留言               ,ヾ( ̄▽ ̄)ByeBye

声明:本站所有文章     ,如无特殊说明或标注           ,均为本站原创发布          。任何个人或组织               ,在未征得本站同意时     ,禁止复制          、盗用                、采集     、发布本站内容到任何网站     、书籍等各类媒体平台                。如若本站内容侵犯了原著者的合法权益      ,可联系我们进行处理     。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
SEO优化教程(学习SEO优化技巧,提高网站排名) js大数字精度丢失怎么处理(JS踩坑实战之19位数Number型精度丢失问题详析)