/** * ease: * 'linear' 动画从头到尾的速度是相同的 * 'ease' 动画以低速开始,然后加快,在结束前变慢 * 'ease-in' 动画以低速开始 * * 'ease-in-out' 动画以低速开始和结束 * 'ease-out' 动画以低速结束 * 'step-start' 动画第一帧就跳至结束状态直到结束 * 'step-end' 动画一直保持开始状态,最后一帧跳到结束状态 */ let config = { size: { width: '560rpx', height: '560rpx' }, // 转盘宽高 bgColors: ['#FFC53F', '#FFED97'], // 转盘间隔背景色 支持多种颜色交替 fontSize: 12, // 文字大小 fontColor: '#C31A34', // 文字颜色 nameMarginTop: 12, // 最外文字边距 nameLength: 6, // 最外文字个数 iconWidth: 32, // 图标宽度 iconHeight: 32, // 图标高度 iconAndTextPadding: 4, // 最内文字与图标的边距 duration: 3000, // 转盘转动动画时长 rate: 1.5, // 由时长s / 圈数得到 border: 'border: 10rpx solid #FEFAE4;', // 转盘边框 ease: 'ease-out' // 转盘动画 }; let preAngle = 0; // 上一次选择角度 let preAngle360 = 0; // 上一次选择角度和360度之间的差 let retryCount = 10; // 报错重试次数 let retryTimer; // 重试setTimeout let drawTimer; // 绘制setTimeout Component({ properties: { // 是否可用 enable: { type: Boolean, value: true }, // 数据 gifts: { type: Array, value: [] }, // 中奖id prizeId: { type: String, value: '' }, // 配置项 传入后和默认的配置进行合并 config: { type: Object, value: {} }, // 抽奖次数 count: { type: Number, default: "" }, }, data: { lotteryCount: null, cost: null, turnCanvasInfo: { width: 0, height: 0 }, size: config.size, giftModule: [], disable: false, canvasImgUrl: '', border: config.border, infos: [] }, methods: { async getCanvasContainerInfo(id) { return new Promise((resolve) => { const query = wx.createSelectorQuery().in(this); query.select(id).boundingClientRect(function (res) { const { width, height } = res; resolve({ width, height }); }).exec(); }); }, async init() { try { const info = await this.getCanvasContainerInfo('#turn'); if (info.width && info.height) { this.setData({ turnCanvasInfo: info }); this.drawTurn(); } else { wx.showToast({ icon: 'nont', title: '获取转盘宽高失败' }) } } catch (e) { if (retryCount <= 0) { return; } retryCount--; if (retryTimer) { clearTimeout(retryTimer); } retryTimer = setTimeout(async () => { await this.init(); }, 100); } }, drawTurn() { const turnCanvasInfo = this.data.turnCanvasInfo; const giftModule = this.properties.gifts; const ctx = wx.createCanvasContext('turn', this); // 计算没个扇区弧度 const radian = Number((2 * Math.PI / giftModule.length).toFixed(2)); // 绘制扇区并记录每个扇区信息 const infos = this.drawSector(radian, giftModule, ctx, turnCanvasInfo); // 记录旋转角度 this.recordTheRotationAngle(infos); // 绘制扇区文本及图片 this.drawTextAndImage(giftModule, ctx, turnCanvasInfo, radian); ctx.draw(false, () => { this.saveToTempPath(turnCanvasInfo); }); }, saveToTempPath(turnCanvasInfo) { if (drawTimer) { clearTimeout(drawTimer); } drawTimer = setTimeout(() => { wx.canvasToTempFilePath({ canvasId: 'turn', quality: 1, x: 0, y: 0, width: turnCanvasInfo.width, height: turnCanvasInfo.height, success: (res) => { this.setData({ canvasImgUrl: res.tempFilePath }); }, fail: (error) => { console.log(error); } }, this); }, 500); }, drawSector(radian, giftModule, ctx, turnCanvasInfo) { const halfRadian = Number((radian / 2).toFixed(2)); let startRadian = -Math.PI / 2 - halfRadian; const angle = 360 / giftModule.length; const halfAngle = angle / 2; let startAngle = -90 - halfAngle; const infos = []; // 绘制扇形 for (let i = 0; i < giftModule.length; i++) { // 保存当前状态 ctx.save(); // 开始一条新路径 ctx.beginPath(); ctx.moveTo(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2); ctx.arc(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2, turnCanvasInfo.width / 2, startRadian, startRadian + radian); if (giftModule[i].bgColor) { ctx.setFillStyle(giftModule[i].bgColor); } else { ctx.setFillStyle(config.bgColors[i % config.bgColors.length]); } ctx.fill(); ctx.closePath(); ctx.restore(); infos.push({ id: giftModule[i].objectId, angle: (startAngle + startAngle + angle) / 2 }); startRadian += radian; startAngle += angle; } return infos; }, drawTextAndImage(giftModule, ctx, turnCanvasInfo, radian) { let startRadian = 0; // 绘制扇形文字和logo for (let i = 0; i < giftModule.length; i++) { // 保存当前状态 ctx.save(); // 开始一条新路径 ctx.beginPath(); ctx.translate(turnCanvasInfo.width / 2, turnCanvasInfo.height / 2); ctx.rotate(startRadian); ctx.translate(-turnCanvasInfo.width / 2, -turnCanvasInfo.height / 2); if (giftModule[i].fontSize) { ctx.setFontSize(giftModule[i].fontSize); } else { ctx.setFontSize(config.fontSize); } ctx.setTextAlign('center'); if (giftModule[i].fontColor) { ctx.setFillStyle(giftModule[i].fontColor); } else { ctx.setFillStyle(config.fontColor); } ctx.setTextBaseline('top'); if (giftModule[i].name) { ctx.fillText(giftModule[i].name, turnCanvasInfo.width / 2, config.nameMarginTop); } if (giftModule[i].subname) { ctx.fillText(giftModule[i].subname ? giftModule[i].subname : '', turnCanvasInfo.width / 2, config.nameMarginTop + config.fontSize + 2); } if (giftModule[i].imgUrl) { ctx.drawImage(giftModule[i].imgUrl, turnCanvasInfo.width / 2 - config.iconWidth / 2, config.nameMarginTop + config.fontSize * 2 + 2 + config.iconAndTextPadding, config.iconWidth, config.iconHeight); } ctx.closePath(); ctx.restore(); startRadian += radian; } }, recordTheRotationAngle(infos) { for (let i = infos.length - 1; i >= 0; i--) { infos[i].angle -= infos[0].angle; infos[i].angle = 360 - infos[i].angle; } // 记录id及滚动的角度 this.setData({ infos: infos }); }, luckDrawHandle() { if (this.data.disable || !this.data.canvasImgUrl) { return; } this.setData({ disable: true }); console.log('开始抽奖') this.triggerEvent('LuckDraw'); }, startAnimation(angle) { if (this.data.lotteryCount - this.data.cost < 0) { this.setData({ disable: false }); this.triggerEvent('NotEnough', '积分不足!'); return; } // 抽奖次数减一 this.setData({ lotteryCount: this.data.lotteryCount - this.data.cost }); const currentAngle = preAngle; preAngle += Math.floor((config.duration / 1000) / config.rate) * 360 + angle + preAngle360; this.animate('#canvas-img', [ { rotate: currentAngle, ease: 'linear' }, { rotate: preAngle, ease: config.ease }, ], config.duration, () => { this.setData({ disable: false }); preAngle360 = 360 - angle; this.triggerEvent('LuckDrawFinish'); }); }, downloadHandle(url) { return new Promise((resolve, reject) => { wx.downloadFile({ url: url, // 仅为示例,并非真实的资源 success: (res) => { // 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容 if (res.statusCode === 200) { resolve(res.tempFilePath); } else { reject(); } }, fail: () => { reject(); } }); }); }, async downloadImg(imgs) { let result; try { const downloadHandles = []; for (const url of imgs) { if (this.isAbsoluteUrl(url)) { // 是网络地址 downloadHandles.push(this.downloadHandle(url)); } else { downloadHandles.push(Promise.resolve(url)); } } result = await Promise.all(downloadHandles); } catch (e) { console.log(e); result = []; } return result; }, clearTimeout() { if (retryTimer) { clearTimeout(retryTimer); } if (drawTimer) { clearTimeout(drawTimer); } }, isAbsoluteUrl(url) { return /(^[a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); }, async initData(data) { let name; let subname; let imgUrls = []; if (this.properties.config) { config = Object.assign(config, this.properties.config); } for (const d of data) { name = d.name; imgUrls.push(d.imgUrl); d.imgUrl = ''; if (name.length > config.nameLength) { d.name = name.slice(0, config.nameLength); subname = name.slice(config.nameLength); if (subname.length > config.nameLength - 2) { d['subname'] = subname.slice(0, config.nameLength - 2) + '...'; } else { d['subname'] = subname; /* console.log('是否开启了概率???', that.data.probability); //开启概率 probability这属性必须要传个ture if (that.data.probability) { r = that._openProbability(); } */ } } } imgUrls = await this.downloadImg(imgUrls); for (let i = 0; i < imgUrls.length; i++) { data[i].imgUrl = imgUrls[i]; } this.setData({ giftModule: data }); await this.init(); } }, observers: { 'gifts': async function (gifts) { if (!gifts || !gifts.length) { return; } await this.initData(gifts); }, 'enable': function (enable) { this.setData({ disable: !enable }); }, 'prizeId': function (id) { if (!id) { this.setData({ disable: false }); return; } try { const infos = this.data.infos; console.log(infos, id) const info = infos.find((item) => item.id == id); console.log(info) this.startAnimation(info.angle); } catch (e) { this.setData({ disable: false }); } }, 'count': function (lotteryCount) { console.log(lotteryCount) this.setData({ lotteryCount }); }, // 'cost': function(cost) { // console.log(cost) // this.setData({ // cost // }); // }, }, lifetimes: { detached() { this.clearTimeout(); } }, pageLifetimes: { hide() { this.clearTimeout(); } } });