Angular 4/Ionic 3幸运轮盘指定位置停止及不重复抽取需求
解决Ionic 3/Angular 4幸运轮盘的精准停位与不重复抽取问题
嘿,我来帮你搞定这两个核心问题:让轮盘精准停在指定位置,以及确保抽到的物品在全部被抽完前不会重复出现。咱们一步步来改代码:
核心需求拆解
你需要实现两个关键功能:
- 精准停位:替代随机减速逻辑,计算目标物品对应的角度,让轮盘刚好停在该位置
- 无重复抽取:维护一个可用物品列表,每次抽取后移除已选中的,耗尽后自动重置
修改后的完整组件代码
import { Component, ViewChild, ElementRef } from '@angular/core'; import { NavController, NavParams } from 'ionic-angular'; @Component({ selector: 'page-wheel', templateUrl: 'wheel.html', }) export class WheelPage { @ViewChild('myCanvas') myCanvas: ElementRef; @ViewChild('spin') spin: ElementRef; public ctx: CanvasRenderingContext2D; color = ['#fbc', '#f88', '#fbc', '#f88', '#fbc', '#f88', "#fbc", "#f67"]; originalLabels = ['Beer', 'Peg', 'Shot', 'Bottle', 'Food', 'Beer1', 'Bomb', "Fire"]; availableLabels: string[]; // 存储当前可抽取的物品 slices: number; sliceDeg: number; deg: number = 0; public speed: number = 0; isSpinning: boolean = false; isStopping: boolean = false; width: number; center: number; targetDeg: number; // 目标停止角度 constructor(public navCtrl: NavController, public navParams: NavParams) { this.slices = this.originalLabels.length; this.sliceDeg = 360 / this.slices; this.resetAvailableItems(); // 初始化可用物品列表 // 设置画布尺寸(修复原代码的画布初始化问题) this.width = 300; this.center = this.width / 2; } ngAfterViewInit(): void { this.ctx = (<HTMLCanvasElement>this.myCanvas.nativeElement).getContext('2d'); // 明确设置画布宽高 (<HTMLCanvasElement>this.myCanvas.nativeElement).width = this.width; (<HTMLCanvasElement>this.myCanvas.nativeElement).height = this.width; this.drawImg(); } // 重置可用物品列表(全部物品重新可抽) resetAvailableItems() { this.availableLabels = [...this.originalLabels]; } // 随机选择一个可用物品,并计算其对应的目标角度 selectTargetItem(): string { // 如果所有物品都被抽过,重置列表 if (this.availableLabels.length === 0) { this.resetAvailableItems(); } const randomIndex = Math.floor(Math.random() * this.availableLabels.length); const selectedItem = this.availableLabels[randomIndex]; // 计算目标角度:让顶部指针刚好指向物品的中间位置 const originalIndex = this.originalLabels.indexOf(selectedItem); // 指针默认在顶部(90度位置),调整角度对齐物品 this.targetDeg = (360 - (originalIndex * this.sliceDeg + this.sliceDeg/2) + 90) % 360; // 让轮盘至少转2圈再停,增强视觉体验 this.targetDeg += 720; // 从可用列表中移除已选中的物品 this.availableLabels.splice(randomIndex, 1); return selectedItem; } deg2rad(deg: number): number { return deg * Math.PI / 180; } drawSlice(deg: number, color: string) { this.ctx.beginPath(); this.ctx.fillStyle = color; this.ctx.moveTo(this.center, this.center); this.ctx.arc(this.center, this.center, this.width / 2, this.deg2rad(deg), this.deg2rad(deg + this.sliceDeg)); this.ctx.lineTo(this.center, this.center); this.ctx.fill(); } drawText(deg: number, text: string) { this.ctx.save(); this.ctx.translate(this.center, this.center); this.ctx.rotate(this.deg2rad(deg)); this.ctx.textAlign = "right"; this.ctx.fillStyle = "#fff"; this.ctx.font = 'bold 20px sans-serif'; // 调整字体大小适配画布 this.ctx.fillText(text, this.center - 20, 10); this.ctx.restore(); } drawImg() { this.ctx.clearRect(0, 0, this.width, this.width); let currentDeg = this.deg % 360; for (var i = 0; i < this.slices; i++) { this.drawSlice(currentDeg, this.color[i]); this.drawText(currentDeg + this.sliceDeg / 2, this.originalLabels[i]); currentDeg += this.sliceDeg; } } anim() { if (this.isSpinning) { this.deg += this.speed; // 加速阶段:速度提升到最大值 if (!this.isStopping && this.speed < 10) { this.speed += 0.5; } // 减速阶段:接近目标角度时逐步减速 if (this.isStopping) { // 计算当前角度与目标角度的最小差值 const diff = Math.abs((this.targetDeg - this.deg + 360) % 360); if (diff < 180) { // 越接近目标,减速越快 this.speed *= 0.95; } // 当速度足够慢且角度精准对齐时,停止动画 if (this.speed < 0.3 && diff < 1) { this.isSpinning = false; this.isStopping = false; this.speed = 0; // 获取最终选中的物品 const finalDeg = this.deg % 360; const ai = Math.floor(((360 - finalDeg - 90) % 360) / this.sliceDeg); alert(`You got:\n${this.originalLabels[ai]}`); return; } } this.drawImg(); window.requestAnimationFrame(this.anim.bind(this)); } }; onSpinClick() { if (!this.isSpinning) { this.isSpinning = true; this.isStopping = false; this.speed = 0; this.selectTargetItem(); // 选择目标物品并计算角度 this.anim(); } } onStopClick(){ if (this.isSpinning && !this.isStopping) { this.isStopping = true; } } }
修改后的HTML代码
<ion-header> <ion-navbar> <ion-title text-center class="fontMont-Regular">Spin Wheel</ion-title> </ion-navbar> </ion-header> <ion-content no-padding class="setBackground"> <ion-row style="margin-top:15%"> <ion-col col-12 text-center> <div id="wheel" style="position: relative;"> <canvas #myCanvas></canvas> <!-- 添加顶部指针标记,让用户清晰看到停止位置 --> <div style="position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 15px solid transparent; border-right: 15px solid transparent; border-top: 30px solid #ff0000;"></div> </div> </ion-col> <ion-col offset-1 col-10 style="display: flex; gap: 10px; margin-top: 20px;"> <button class="spinButton" (click)="onSpinClick()" [disabled]="isSpinning">Spin!</button> <button class="stopButton" (click)="onStopClick()" [disabled]="!isSpinning || isStopping">Stop!</button> </ion-col> </ion-row> </ion-content>
关键改动说明
1. 精准停位实现
- 新增
targetDeg变量存储目标停止角度,通过selectTargetItem计算选中物品对应的角度 - 重构动画逻辑:先加速到最大值,收到停止指令后,根据与目标角度的差值逐步减速,直到精准停在目标位置
- 强制轮盘至少转2圈再停止,提升用户体验
2. 无重复抽取实现
- 新增
availableLabels数组存储当前可抽取的物品,originalLabels保留原始列表 selectTargetItem函数负责随机选择可用物品,移除后添加到已抽列表,当可用列表为空时自动重置- 确保所有物品都被抽过一次后才会重复出现
3. 其他优化
- 修复了画布尺寸初始化问题,确保轮盘显示正常
- 添加了顶部指针标记,让用户更清晰识别停止位置
- 优化按钮状态,防止重复点击导致的逻辑混乱
内容的提问来源于stack exchange,提问作者chirag kataria
相关产品推荐
相关产品推荐

