摘要:最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路需求转盘根据奖品数量不同而有变化目录结构由于业务需要所以开发了两个版本抽奖,和,不过部分只能替换图片,没有功能逻辑。
最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路
需求1、转盘根据奖品数量不同而有变化 2、canvas目录结构
由于业务需要所以开发了两个版本抽奖,dom和canvas,不过editor.js部分只能替换图片,没有功能逻辑。
需要注意的是此目录隐藏了一个动态数据类(dataStore),因为集成在项目里了,所以没有体现。
Spirts精灵类生成实例,会包括基础属性:width、height、x、y和方法:setOpacity、drawCircular、setRotate、draw
下面是几个重要的精灵构造器:背景、转盘背景和每一个奖品
/* * 精灵核心 基类 * */ class Spirt { constructor({}) {} // 精灵透明度调节 setOpacity(opy, callback) {} // 画圆形图片 drawCircular(fn) {} // 精灵旋转调节 setRotate() {} // 画精灵 draw() {} } // 背景 class Bg extends Spirt { constructor({ ...args }) { super({ ...args }); if (args.height == "100%") { this.height = this.canvas.height; } } } // 转盘背景 class Turn extends Spirt { constructor({ ...args }) { super({ ...args }); } draw() { this.drawCircular(() => { super.draw(); }); } } // 每一个奖品 class Item extends Spirt { constructor({ ...args }, rid) { super({ ...args }); this.rid = rid; } draw(angle, x, y) { this.setRotate(angle, () => super.draw(), x, y); } }Config
基础数据类,包括基础数据:转盘分块、角度、半径、每一块对应奖品、旋转总时长、旋转速度等
主要说一下转盘分块:如果符合规律,就用函数代替,如果不符合规律就用映射
let layout = { 1: [1, 10, 1, 10, 1, 10], 2: [1, 2, 10, 1, 2, 10], 3: [1, 10, 2, 10, 3, 10], 4: [2, 10, 3, 10, 4, 10, 1, 10], 5: [2, 3, 4, 10, 5, 10, 1, 10], 6: [2, 3, 4, 10, 5, 6, 1, 10], 7: [3, 4, 10, 5, 6, 7, 10, 1, 2, 10], 8: [3, 4, 10, 5, 6, 7, 8, 10, 1, 2] };
下面为部分代码
class Config { constructor(prize = new Array(3), resImg) { this.awards_len = prize.length >= 7 ? 10 : prize.length >= 4 ? 8 : 6; this.awards_angle = 360 / this.awards_len; this.awards_r = 320; this.awards_cir = 2 * Math.PI * this.awards_r; let nums = { 6: 2.5, 8: 2, 10: 2 }; this.awards_item_margin = 40; this.award_item_size = this.awards_cir / this.awards_len / nums[this.awards_len]; this.duration = 2000; // 奖品详情 this.awards = getAwards(resImg, prize.length); } } /** * 获取奖品列表 * @param {*} num */ function getAwards(resImg, num) { let arr = layout[num]; return arr.map(rid => { let res = resImg[mapAwards[rid]]; return { rid, res, className: mapAwards[rid] }; }); }Res
资源类主要做一些图片初始化的操作
// 获取游戏资源 class Res extends Resource { constructor(dataStore) { super({ dataStore }); let { gameJson } = dataStore; this.res = { ...gameJson.staticSpirts.BG }; this.dataStore = dataStore; } // 编辑页面改变页面图片能力。 setImg(data) { this.res[data.num].imgUrl = data.imgUrl; if (["BG", "TITLE", "TURNTABLE_BG", "PLAYBTN"].includes(data.num)) { $(`.turnTableNew_${data.num}`).css( "background-image", `url("${HOST.FILE + data.imgUrl}")` ); } else { $(`.turnTableNew_${data.num}`).attr( "src", `${HOST.FILE + data.imgUrl}` ); } return { staticSpirts: this.res }; } }Director
导演类,主要操作的是转盘动画的逻辑
主要逻辑是:
1、addCLick: canvas添加点击事件
2、drawStatic:画静态元素
3、drawZhuanPan:这个为多带带canvas,group内部包括画转盘,奖品
4、drawPlayBtn: 画按钮
5、当点击抽奖按钮执行updatedRotate函数让多带带转盘canvas旋转即可
6、当旋转角度和获取奖品角度一致时停止
class turnTable extends Director { constructor(dataStore) { let { gameManager } = dataStore; super(gameManager); // 从仓库中获取基础数据,canvas和config总配置 this.dataStore = dataStore; this.canvas = dataStore.canvas; this.config = dataStore.$gameConfig; // 当前抽奖的一些基础数据 this.angle = 0; this.isAnimate = true; this.lastTime = 0; this.num = 0; this.addCLick(); } // 抽奖结束,需要初始化抽奖 initGame() { this.state = this.START; this.angle = 0; this.num = 0; this.prizeId = null; this.isAnimate = true; this.turnAudio.pause(); this.drawAllElements(this.res, this.set); } /** * 画所有元素 * @param {*} store * @param {*} res */ drawAllElements(res, set) { this.res = res; this.set = set; this.drawStatic(res); this.drawZhuanPan(this.angle); this.drawPlayBtn(this.canvas, res); } /** * 画静态元素 */ drawStatic(res) { ["BG", "TITLE"].forEach(item => { let str = item.toLowerCase(); str = str.replace(str[0], str[0].toUpperCase()); let ele = new Spirts[str]({ canvas: this.canvas, ...res[item] }); ele.draw(); }); } // 画转盘 drawZhuanPan(angle) { this.group = new Spirts["Group"]({ canvas: this.canvas, ...this.res["TURNTABLE_BG"] }); this.items = this.drawDynamic(this.group.group_canvas, this.res); this.group.draw( angle, +this.res["TURNTABLE_BG"].x + +this.res["TURNTABLE_BG"].width / 2, +this.res["TURNTABLE_BG"].y + +this.res["TURNTABLE_BG"].height / 2 ); } // 画动态元素 drawDynamic(canvas, res) { let set = this.set; let items = []; // 转盘背景1,装饰物 let turnBg = new Spirts["Turn"]({ canvas, img: res["TURNTABLE_BG"].img, width: res["TURNTABLE_BG"].width, height: res["TURNTABLE_BG"].height, x: 0, y: 0 }); turnBg.draw(); // 转盘背景2,盘面 let turnPan = new Spirts["Turn"]({ canvas, img: res["TURNTABLE_PAN"].img, width: res["TURNTABLE_PAN"].width, height: res["TURNTABLE_PAN"].height, x: (res["TURNTABLE_BG"].width - res["TURNTABLE_PAN"].width) / 2, y: (res["TURNTABLE_BG"].height - res["TURNTABLE_PAN"].height) / 2 }); turnPan.draw(); for (let i = 0; i < set.awards_len; i++) { // 每一个奖品 let item = new Spirts["Item"]( { canvas, img: set.awards[i].res.img, width: set.award_item_size, height: set.award_item_size, x: turnBg.width / 2 - set.award_item_size / 2, y: (turnBg.height - turnPan.height) / 2 + set.awards_item_margin }, set.awards[i].rid ); item.draw( set.awards_angle / 2 + set.awards_angle * i, turnBg.width / 2, turnBg.height / 2 ); // 画线 let line = new Spirts["Item"]({ canvas, img: res["LINE"].img, width: res["LINE"].width, height: res["LINE"].height, x: turnBg.width / 2 - res["LINE"].width / 2, y: (turnBg.height - turnPan.height) / 2 }); line.draw( set.awards_angle * i, turnBg.width / 2, turnBg.height / 2 ); // 放到items数组内,后期转盘停止校验用 items.push(item); } return items; } // 画按钮 drawPlayBtn(canvas, res) { let playBtn = new Spirts["PlayBtn"]({ canvas, ...res["PLAYBTN"] }); playBtn.draw(); this.playBtn = playBtn; } // 点击事件 addCLick() { let initX, isClickState, cScale = this.config["cScale"] || 1; this.canvas.addEventListener(tapstart, event => { initX = event.targetTouches ? event.targetTouches[0].clientX : event.offsetX / cScale; let y = event.targetTouches ? event.targetTouches[0].clientY : event.offsetY / cScale; isClickState = isCheck.call(this.playBtn, initX, y); // 点击回调 if (isClickState && this.isAnimate) { /** * 按钮不可点击 * 初始化总时长 * 初始化速度 * 初始化当前时间 */ this.isAnimate = false; this.set.is_animate = true; this.set.jumping_total_time = Math.random() * 1000 + this.set.duration; this.set.speed = (this.set.jumping_total_time / 2000) * 10; this.lastTime = new Date().getTime(); this.run(); this.getPrize() .then(res => { if (!res) { this.prizeId = 10; return; } this.prizeId = +res.prizeLevel + 1; }) .catch(_ => { this.prizeId = 10; this.initGame(); this.state = this.END; }); } }); } updatedRotate() { let curTime = new Date().getTime(), set = this.set, speed = 1; /** * 转盘停止,需要满足一下条件 * 1.大于总时间 * 2.有奖品id * 3.速度降为1 * 4.转盘角度对应奖品id位置 * 角度做了容错处理,当前角度范围中心位置,偏移量为5 * 公式:通过旋转角度计算当前奖品index * 通过items奖品列表计算当前奖品rid * rid和prizeId对比,如果结束抽奖 */ if ( curTime - this.lastTime >= set.jumping_total_time && this.prizeId && speed == 1 ) { let resultAngle = 360 - (this.angle % 360); let index = (resultAngle / set.awards_angle) >> 0; let centerAngle = set.awards_angle * (index + 0.5); if ( this.items[index].rid == this.prizeId && (resultAngle > centerAngle - 5) & (resultAngle < centerAngle + 5) ) { this.comAudio.play(); this.state = this.PAUSE; } } this.num++; speed = Math.max( set.speed - (18 * this.num * (set.speed - 1)) / set.jumping_total_time, 1 ); this.angle += speed; this.drawAllElements(this.res, this.set); } // 渲染画布 render() { switch (this.state) { case this.START: this.updatedRotate(); break; case this.ERROR: break; case this.PAUSE: this.state = this.END; setTimeout(() => { this.showResult(); this.initGame(); }, 1000); break; case this.END: // 打开指定页面 break; } } }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/106906.html
摘要:圆盘抽奖应用页面圆盘抽奖应用演示抱歉浏览器不支持。 HTML5 Canvas圆盘抽奖应用DEMO html页面 HTML5 Canvas圆盘抽奖应用DEMO演示 抱歉!浏览器不支持。 抱歉!浏览器不支持。 抱歉!浏览器...
摘要:圆盘抽奖应用页面圆盘抽奖应用演示抱歉浏览器不支持。 HTML5 Canvas圆盘抽奖应用DEMO html页面 HTML5 Canvas圆盘抽奖应用DEMO演示 抱歉!浏览器不支持。 抱歉!浏览器不支持。 抱歉!浏览器...
摘要:圆盘抽奖应用页面圆盘抽奖应用演示抱歉浏览器不支持。 HTML5 Canvas圆盘抽奖应用DEMO html页面 HTML5 Canvas圆盘抽奖应用DEMO演示 抱歉!浏览器不支持。 抱歉!浏览器不支持。 抱歉!浏览器...
阅读 1554·2021-11-04 16:11
阅读 3283·2021-09-09 11:33
阅读 1544·2019-08-30 15:54
阅读 602·2019-08-30 15:44
阅读 3148·2019-08-30 15:43
阅读 2490·2019-08-30 13:06
阅读 1683·2019-08-29 17:00
阅读 871·2019-08-29 15:33