最近工做中重構了抽獎轉盤,給你們提供一個開發轉盤抽獎的思路css
一、轉盤根據獎品數量不一樣而有變化 二、canvas
因爲業務須要因此開發了兩個版本抽獎,dom和canvas,不過editor.js部分只能替換圖片,沒有功能邏輯。canvas
須要注意的是此目錄隱藏了一個動態數據類(dataStore),由於集成在項目裏了,因此沒有體現。數組
精靈類生成實例,會包括基礎屬性:width、height、x、y和方法:setOpacity、drawCircular、setRotate、drawdom
下面是幾個重要的精靈構造器:背景、轉盤背景、單獨獎品和轉盤組函數
主要說一下轉盤組group概念,其實就是單獨生成一個canvas,把須要轉動的精靈放在其中,最後旋轉group。動畫
/* * 精靈核心 基類 * */ 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); } } // 轉盤組 class Group { constructor({ canvas, width, height, x, y }) { this.ctx = canvas.getContext('2d'); this.createElement(width, height); this.x = x; this.y = y; } createElement(width, height) { this.group_canvas = document.createElement('canvas'); this.group_ctx = this.group_canvas.getContext('2d'); this.group_canvas.width = +width; this.group_canvas.height = +height; this.group_canvas.fillStyle = 'red'; } draw(angle, x, y) { this.setRotate( angle, () => this.ctx.drawImage(this.group_canvas, this.x, this.y), x, y ); } // 精靈旋轉調節 setRotate( angle, fn, x = this.canvas.width / 2, y = this.canvas.height / 2 ) { this.ctx.save(); this.ctx.beginPath(); this.ctx.translate(x, y); // 將繪圖原點移到畫布中點 this.ctx.rotate((Math.PI / 180) * angle); // 旋轉角度 this.ctx.translate(-x, -y); // 將畫布原點移動 fn && fn(); this.ctx.closePath(); this.ctx.restore(); } }
基礎數據類,包括基礎數據:轉盤分塊、角度、半徑、每一塊對應獎品、旋轉總時長、旋轉速度等this
主要說一下轉盤分塊:若是符合規律,就用函數代替,若是不符合規律就用映射url
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] };
下面爲部分代碼spa
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] }; }); }
資源類主要作一些圖片初始化的操做rest
// 獲取遊戲資源 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 }; } }
導演類,主要操做的是轉盤動畫的邏輯
主要邏輯是:
一、addCLick: canvas添加點擊事件
二、drawStatic:畫靜態元素
三、drawZhuanPan:這個爲單獨canvas,group內部包括畫轉盤,獎品
四、drawPlayBtn: 畫按鈕
五、當點擊抽獎按鈕執行updatedRotate函數讓單獨轉盤canvas旋轉便可
六、當旋轉角度和獲取獎品角度一致時中止
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; } } }