旋转大转盘小程序

Index.wxml

<!-- pages/index.wxml -->

<view class="container">

<view class="turntable">

<view

class="wheel"

style="transform: rotate({{rotateAngle}}deg); transition: transform {{duration}}s cubic-bezier(0.25, 0.1, 0.25, 1);">

<view

wx:for="{{prizes}}"

wx:key="index"

class="prize-item"

style="transform: rotate({{index * (360 / prizes.length)}}deg);">

<view class="prize-text">

<view class="prize-level">{{item.level}}</view>

<view class="prize-title">{{item.title}}</view>

</view>

</view>

</view>

<!-- 指针 -->

<view class="pointer">

<view class="pointer-arrow"></view>

</view>

</view>

<button class="spin-btn" bindtap="spin" disabled="{{isSpinning}}">开始抽奖</button>

</view>

Index.wxss

/* pages/index.wxss */

.container {

display: flex;

flex-direction: column;

align-items: center;

justify-content: center;

height: 100vh;

background: #f8f8f8;

}

.turntable {

position: relative;

width: 300px;

height: 300px;

margin-bottom: 40px;

.wheel {

width: 100%;

height: 100%;

border-radius: 50%;

background: conic-gradient(

#ff6b6b 0% 12.5%,

#4ecdc4 12.5% 25%,

#45b7d1 25% 37.5%,

#96ceb4 37.5% 50%,

#ffeead 50% 62.5%,

#ff9f43 62.5% 75%,

#f15bb5 75% 87.5%,

#00bbf9 87.5% 100%

);

position: relative;

box-shadow: 0 10px 30px rgba(0,0,0,0.2);

overflow: hidden;

}

.prize-item {

position: absolute;

top: 0;

left: 0;

width: 100%;

height: 50%;

transform-origin: 50% 100%;

}

.prize-text {

position: absolute;

bottom: 22%;

left: 50%;

transform: translateX(-50%) rotate(90deg);

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

gap: 12px;

font-weight: bold;

color: white;

text-shadow: 1px 1px 2px rgba(0,0,0,0.3);

width: 60px;

white-space: normal;

text-align: center;

padding: 8px 4px;

overflow: hidden;

}

.prize-level {

font-size: 14px;

line-height: 18px;

margin: 0;

}

.prize-title {

font-size: 12px;

line-height: 16px;

margin: 0;

}

.pointer {

position: absolute;

top: 50%;

left: 50%;

transform: translate(-50%, -50%);

z-index: 10;

width: 60px;

height: 60px;

background: #fff;

border-radius: 50%;

box-shadow: 0 0 10px rgba(0,0,0,0.2);

display: flex;

justify-content: center;

align-items: center;

}

.pointer-arrow {

width: 0;

height: 0;

border-left: 15px solid transparent;

border-right: 15px solid transparent;

border-bottom: 30px solid #ff4757;

position: absolute;

top: -30px;

}

.spin-btn {

padding: 16px 40px;

font-size: 18px;

background: linear-gradient(135deg, #ff6b6b, #ee5253);

color: white;

border-radius: 25px;

border: none;

box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);

}

.spin-btn:disabled {

background: #ccc;

box-shadow: none;

}

Index.js

// pages/index.js

Page({

data: {

// 奖品设置:8个奖品(每项分为等级 level 与具体标题 title)

prizes: [

{ level: "一等奖", title: "价值大礼", color: "#ff6b6b" },

{ level: "未中奖", title: "谢谢参与", color: "#4ecdc4" },

{ level: "二等奖", title: "精美礼品", color: "#45b7d1" },

{ level: "再来一次", title: "再来一次", color: "#96ceb4" },

{ level: "三等奖", title: "幸运奖", color: "#ffeead" },

{ level: "安慰奖", title: "小礼品", color: "#ff9f43" },

{ level: "特等奖", title: "超值大奖", color: "#f15bb5" },

{ level: "幸运奖", title: "神秘礼", color: "#00bbf9" }

],

rotateAngle: 0,

duration: 4,

isSpinning: false

},

spin() {

if (this.data.isSpinning) return;

this.setData({ isSpinning: true });

// 随机生成中奖结果(0-7)

const winningIndex = Math.floor(Math.random() * this.data.prizes.length);

// 计算需要旋转的角度(修正:按当前角度计算增量,避免重复计入 currentRotation)

// 基础圈数和每个奖品角度

const baseRotation = 360 * 5; // 至少旋转5圈

const itemAngle = 360 / this.data.prizes.length;

// 中奖项中心角度(未旋转时)

const targetCenter = winningIndex * itemAngle + itemAngle / 2;

// 当前角度模360

const currentRotation = this.data.rotateAngle;

const currentMod = ((currentRotation % 360) + 360) % 360;

// 需要增量使得 (targetCenter + currentMod + delta) %360 === 0

const offset = (360 - ((targetCenter + currentMod) % 360)) % 360;

const delta = baseRotation + offset;

const finalRotation = currentRotation + delta;

// 设置动画

this.setData({

rotateAngle: finalRotation,

duration: 4

});

// 动画结束后显示结果并进行诊断(以computedIndex为准,保证与视觉一致)

setTimeout(() => {

this.setData({ isSpinning: false });

// 诊断:计算最终角度并推断停在指针处的索引

const angleMod = ((finalRotation % 360) + 360) % 360;

let computedIndex = 0;

let minDiff = 360;

for (let i = 0; i < this.data.prizes.length; i++) {

const center = ((i * itemAngle + itemAngle / 2 + finalRotation) % 360 + 360) % 360;

const diff = Math.min(Math.abs(center - 0), Math.abs(center - 360));

if (diff < minDiff) { minDiff = diff; computedIndex = i; }

}

const prize = this.data.prizescomputedIndex;

const intended = this.data.prizeswinningIndex;

console.log('winningIndex', winningIndex, 'intendedPrize', `{intended.level} --- {intended.title}`, 'finalRotation', finalRotation, 'angleMod', angleMod);

console.log('computedIndex', computedIndex, 'computedPrize', `{prize.level} --- {prize.title}`);

console.log(`恭喜!你抽中了:{prize.level} --- {prize.title}`);

// 可选:添加成功提示(使用实际停位的奖项)

wx.showToast({

title: `抽中:{prize.level} --- {prize.title}`,

icon: 'success',

duration: 2000

});

}, 4000); // 与动画时长一致

}

});

相关推荐
道友可好1 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
Tian_Hang1 小时前
Eclipse Ditto 的权限策略
java·服务器·前端·网络·ide·ubuntu·eclipse
lichenyang4532 小时前
从 0 新增一个 `has.echo`:我如何理解小程序容器里的 API 调用链路
前端
leptune2 小时前
Mac 使用 Microsoft Word 批量将 DOCX 转 PDF(保持原排版)
前端
大龄秃头程序员3 小时前
Flutter 长列表 1 万条滑动卡顿治理:Baseline vs Optimized 可跑 Demo
前端
yingyima3 小时前
Git 核心命令速查:掌握底层原理与实战技巧
前端
黄林晴3 小时前
Kuikly 是什么?和KMP有什么关系?
android·前端
HokKeung3 小时前
Git Hooks 和 Husky 怎么选
前端·前端工程化
李明卫杭州3 小时前
Vue 3 响应式三剑客:ref、shallowRef、reactive,你真的用对了吗?
前端