微信小程序转盘抽奖demo代码
mini云码 发布日期: 2025-11-04 10:10
下面是小程序的一个转盘抽奖的例子,奖品的内容可以随意修改。我把代码的几个文件都列出来,包括页面代码、样式代码和js代码。
mypage.wxml代码:
<view class="container"><view class="wheel-container">
<view class="prize-wheel">
<image class="canvas" src="{{wheelImg}}"></image></view><view class="pointer" style="transform: rotate({{pointerAngle}}deg)"></view>
</view><button class="btn" bindtap="startRotate" disabled="{{isRotating}}">开始</button><button class="btn edit-btn" bindtap="editPrize">编辑</button>
<modal title="编辑奖项" hidden="{{!showModal}}" bindcancel="closeModal">
<view class="edit-area">
<!-- <view class="color-picker-slider">
<view>R: <slider min="0" max="255" value="{{r}}" bindchange="onColorSliderChange" data-channel="r"/></view>
<view>G: <slider min="0" max="255" value="{{g}}" bindchange="onColorSliderChange" data-channel="g"/></view>
<view>B: <slider min="0" max="255" value="{{b}}" bindchange="onColorSliderChange" data-channel="b"/></view>
<view class="color-preview" style=" color: rgb(189, 99, 197);">{{r}}, {{g}}, {{b}});"></view>
</view> -->
<view class="prize-item" wx:for="{{prizeList}}" wx:key="index"><input
class="prize-name"
placeholder="奖项名称"
value="{{item.name}}"
data-index="{{index}}"bindinput="updatePrizeName"
/><input
class="color-picker"
type="color"
value="{{item.color}}"
data-index="{{index}}"bindchange="updatePrizeColor"
/>
<button class="delete-btn" data-index="{{index}}" bindtap="deletePrizeItem">-</button>
</view>
</view>
<button class="btn add-btn" bindtap="addNewPrize">+</button><button class="btn save-btn" bindtap="savePrizeList">保存</button>
</modal>
</view>mypage.js代码:
Page({
data: {
prizeList: [{
name: '一等奖',
color: '#FFD700'
},
{
name: '谢谢参与',
color: '#C0C0C0'
},
{
name: '二等奖',
color: '#CD7F32'
},
{
name: '谢谢参与',
color: '#C0C0C0'
},
{
name: '三等奖',
color: '#A0522D'
},
{
name: '谢谢参与',
color: '#C0C0C0'
},
],
prizeListStr: '',
showModal: false,
rotateAngle: 0, // 用于模拟转盘动画,实际并未使用animation: null, // 未使用pointerAngle: 0, // 指针角度isRotating: false, // 是否正在旋转animationDuration: 5000, // 动画持续时间 (毫秒)showColorPickerVisible: false,
currentPrizeIndex: null,
r: 0,
g: 0,
b: 0,
colorsList: [
'#FFD700', '#C0C0C0', '#CD7F32', '#A0522D', '#DC143C', '#FF69B4',
'#BA55D3', '#7B68EE', '#6A5ACD', '#483D8B', '#4682B4', '#00CED1',
'#5F9EA0', '#2E8B57', '#9ACD32', '#FFFF00', '#FFA500', '#FF4500',
'#8B4513', '#D2691E', '#B8860B', '#808000', '#556B2F', '#228B22',
'#008000', '#006400', '#90EE90', '#00FF7F', '#00FA9A', '#20B2AA',
'#235788', '#697656', '#D935E9', '#B69754', '#BFA476', '#BAC289',
'#983471', '#EA4586', '#EABCDE', '#ACBEAD', '#825673', '#65C946',
],
},
onLoad() {
this.drawPrizeWheel();
},
onReady() {
},
onColorSliderChange(e) {
const channel = e.currentTarget.dataset.channel;
const value = e.detail.value;
this.setData({
[channel]: value
});
},
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});
},
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 });
if (progress < 1) {
setTimeout(rotateAnimation, 1000 / frameRate);
} else {
this.stopRotate();
}
};
rotateAnimation();
},
stopRotate() {
let winningIndex = null;
const prizeCount = this.data.prizeList.length;
const anglePerItem = 360 / prizeCount;
const finalAngle = this.data.pointerAngle;
const pointerAngle = (finalAngle) % 360;
// 循环遍历每个奖项区域,判断指针是否落在该区域内for (let i = 0; i < prizeCount; i++) {
const { startAngle, endAngle } = this.data.prizeList[i];
// 处理跨越 0 度的情况,跨越0度就是该区域的开始角度大于结束角度,如340-20if (startAngle > endAngle) {
if (pointerAngle >= startAngle || pointerAngle < endAngle) {
winningIndex = i;
break;
}
} else {
if (pointerAngle >= startAngle && pointerAngle < endAngle) {
winningIndex = i;
break;
}
}
}
this.setData({
isRotating: false
});
const winningPrize = this.data.prizeList[winningIndex].name;
wx.showModal({
title: "恭喜!",
content: `您获得了${winningPrize}!`,
showCancel: false,
});
},
editPrize() {
this.setData({
showModal: true
});
},
closeModal() {
this.setData({
showModal: false
});
},
addNewPrize() {
const colorsLength = this.data.colorsList.length;
const random = Math.floor(Math.random() * colorsLength);
this.setData({
prizeList: [...this.data.prizeList, {
name: '',
color: this.data.colorsList[random] || '#000000'
}],
});
},
deletePrizeItem(e) {
const index = e.currentTarget.dataset.index;
let updatedPrizeList = [...this.data.prizeList];
updatedPrizeList.splice(index, 1); // 从数组中移除对应奖项this.setData({
prizeList: updatedPrizeList,
});
},
updatePrizeName(e) {
const index = e.currentTarget.dataset.index;
const value = e.detail.value;
let updatedPrizeList = this.data.prizeList;
updatedPrizeList[index].name = value;
this.setData({
prizeList: updatedPrizeList,
});
},
shufflePrizeList() {
let updatedPrizeList = [...this.data.prizeList];
for (let i = updatedPrizeList.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[updatedPrizeList[i], updatedPrizeList[j]] = [updatedPrizeList[j], updatedPrizeList[i]];
}
this.setData({
prizeList: updatedPrizeList,
});
},
updatePrizeColor(e) {
const index = e.currentTarget.dataset.index;
const color = e.detail.value;
let updatedPrizeList = this.data.prizeList;
updatedPrizeList[index].color = color;
this.setData({
prizeList: updatedPrizeList,
});
},
savePrizeList() {
// 将 prizeList 转换为 JSON 字符串并存储// 这里可以根据你的需求修改存储方式const prizeListStr = JSON.stringify(this.data.prizeList);
// console.log("保存的 JSON 字符串:", prizeListStr);this.shufflePrizeList(); //打乱奖项的排序,实现随机性this.closeModal();
this.drawPrizeWheel(); // 重新绘制转盘
},
})mypage.wxss代码:
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: auto;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom); /*兼容 IOS<11.2*/padding-bottom: env(safe-area-inset-bottom); /*兼容 IOS>11.2*/
}
.wheel-container {
margin-top: 50rpx;
width: 300px;
height: 300px;
position: relative;
}
.prize-wheel {
width: 100%;
height: 100%;
}
.canvas {
width: 100%;
height: 100%;
}
.pointer {
width: 0px;
height: 0px;
border-bottom: 50px solid #ff0000;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
/* */position: absolute;
bottom: 50%;
left: 50%;
transform-origin: 50% 100%; /* 设置旋转中心点为指针底部 */z-index: 10;
margin-left: -10px; /* 添加 margin-left */margin-top: -10px;
}
.pointer::before{
content: "";
width: 20px;
height: 20px;
border: 0;
padding: 0;
border-radius: 50%;
background-color: #ff0000;
position: absolute;
bottom: -60px;
left: -10px;
z-index: 10;
}
.btn {
margin: 10px;
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: #fff;
font-size: 16px;
}
.edit-btn {
background-color: #28a745;
}
.save-btn {
background-color: #28a745;
margin-top: 20px;
}
.add-btn {
background-color: #1989fa;
margin-top: 10px;
}
.color-preview {
width: 50px;
height: 20px;
border: 1px solid #ccc;
}
.edit-area {
padding: 10rpx;
overflow: auto;
height: auto;
max-height: 620rpx;
}
.delete-btn {
background-color: #dc3545;
/* 红色背景 */color: #fff;
/* 白色文字 */border: none;
border-radius: 50%;
/* 圆形按钮 */display: flex;
align-items: center;
justify-content: center;
width: 55rpx !important;
height: 55rpx !important;
padding: 0;
line-height: 55rpx;
text-align: center;
font-size: 12px;
margin-left: 5px;
}
.prize-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.prize-name,
.color-picker {
flex: 1;
padding: 5px;
border: 1px solid #ccc;
margin-right: 5px;
}上面的代码还可以继续做一个优化,就是js代码里有一个奖品的列表prizeList,你可以自己替换prizeList里面的奖品
为了增加随机性,你可以每次抽奖前或抽奖后,打乱奖品的顺序,下面是一个随机打乱奖品顺序的函数,你也可以使用这个函数打乱奖品的顺序:
shufflePrizeList() {
let updatedPrizeList = [...this.data.prizeList];
for (let i = updatedPrizeList.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[updatedPrizeList[i], updatedPrizeList[j]] = [updatedPrizeList[j], updatedPrizeList[i]];
}
this.setData({
prizeList: updatedPrizeList,
});
}
