这个功能可以分几步实现:
1. 界面设计:
转盘区域: 使用 canvas 绘制转盘,可配置扇形数量、颜色、文字等。
按钮: "开始/停止" 按钮控制转盘转动。
编辑按钮: 点击弹出弹窗,编辑转盘项目。
中奖弹窗: 显示中奖结果。
2. 数据结构:
使用数组存储转盘项目数据,例如:
const prizeList = [
{ name: '一等奖', color: '#FFD700' },
{ name: '二等奖', color: '#C0C0C0' },
{ name: '三等奖', color: '#CD7F32' },
// ... 其他奖项
];
3. 功能实现:
绘制转盘:
根据 prizeList 数据计算每个扇形的角度。
使用 canvas API 绘制扇形、文字、边框等。
drawPrizeWheel() {
// 创建离屏 2D canvas 实例,创建canvas元素
const canvas = wx.createOffscreenCanvas({type: '2d', width: 300, height: 300});
// 获取 context。注意这里必须要与创建时的 type 一致const ctx = canvas.getContext('2d');
const centerX = 150;
const centerY = 150;
const radius = 130;
const prizeCount = this.data.prizeList.length;
const anglePerItem = 360 / prizeCount;
// 记录当前角度,初始为 0/90 度
let currentAngle = 90;
for (let i = 0; i < prizeCount; i++) {
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(
centerX,
centerY,
radius,
(i * anglePerItem * Math.PI) / 180,
((i + 1) * anglePerItem * Math.PI) / 180,
);
ctx.closePath();
ctx.fillStyle = this.data.prizeList[i].color;
ctx.fill();
// --- 绘制文字 ---
ctx.save();
// 保存当前画布状态
ctx.font = '12px sans-serif';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const textAngle = (i * anglePerItem + anglePerItem / 2) * (Math.PI) / 180;
const textX = centerX + radius * 0.7 * Math.cos(textAngle);
const textY = centerY + radius * 0.7 * Math.sin(textAngle);
ctx.fillText(this.data.prizeList[i].name, textX, textY);
ctx.restore();
// 恢复之前的画布状态// --- 文字绘制结束 ---
// 计算当前奖项区域的结束角度
let endAngle = currentAngle + anglePerItem;
if(endAngle > 360){
endAngle %= 360;
}
// 计算当前奖项区域的起始角度和结束角度
// 将角度信息存储到 prizeList 数组中
this.data.prizeList[i].startAngle = currentAngle;
this.data.prizeList[i].endAngle = endAngle;
// 更新 currentAngle,准备绘制下一个区域
currentAngle = endAngle;
}
// 将canvas转换为DataURL格式的图片var dataURL = canvas.toDataURL('image/png', 1);this.setData({wheelImg: dataURL});
},
代码解释:
初始化 currentAngle: 在循环开始之前,将 currentAngle 初始化为 90,表示从 90 度开始绘制。因为canvas 绘制圆弧的起始角度是水平向右的 x 轴正方向,而不是竖直向上的 y 轴正方向。所以这里实际起始角度是从90度开始。
计算结束角度: 在每次循环中,根据 currentAngle 和 anglePerItem 计算当前奖项区域的结束角度 endAngle。
绘制扇形: 使用 currentAngle 和 endAngle 绘制当前奖项区域的扇形。将 currentAngle 和 endAngle 存储到 prizeList 数组中,方便后续判断中奖区域。更新 currentAngle: 将 currentAngle 更新为 endAngle,以便绘制下一个奖项区域。最后把canvas转换为base64图片地址。
记录角度信息: 在 drawPrizeWheel 方法中,我们为prizeList数组中每个奖项对象添加了 startAngle 和 endAngle 属性,用来存储该奖项区域的起始和结束角度。
判断指针位置: 在 stopRotate 方法中,我们循环遍历 prizeList 数组,并根据每个奖项的 startAngle 和 endAngle 判断指针是否落在该区域内。
注意处理了跨越 0 度的情况,如果 startAngle 大于 endAngle,则表示该区域跨越了 0 度,需要分别判断指针是否大于等于 startAngle 或者小于 endAngle。
通过以上修改,奖项区域将从 0 度开始绘制,并且每个区域的角度信息会被正确记录在 prizeList 数组中,然后在 stopRotate方法中准确判断指针落在哪个区域,从而确定中奖结果。
转盘转动:
使用 setInterval/setTimeout 或 requestAnimationFrame 实现动画效果。
控制转速和停止位置。
<view class="pointer" style="transform: rotate({
{pointerAngle}}deg)"></view>
//开始旋转
startRotate() {
if (this.data.isRotating) return;
this.setData({ isRotating: true });
// 生成随机旋转圈数(至少旋转 6 圈)
const randomRounds = 6 + Math.floor(Math.random() * 3);
// 生成随机停止角度
const finalAngle = Math.floor(Math.random() * 360);
// 计算总旋转角度
const totalRotation = randomRounds * 360 + finalAngle;
// 使用 setInterval 实现动画
const startTime = Date.now();const frameRate = 80;
const rotateAnimation = () => {
const currentTime = Date.now();
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / this.data.animationDuration, 1);
// 使用 ease-out 动画曲线
const easeOut = (t) => 1 - Math.pow(1 - t, 3);
// 计算当前角度
const currentAngle = easeOut(progress) * totalRotation;
this.setData({ pointerAngle: currentAngle });
i