侧边栏壁纸
  • 累计撰写 54 篇文章
  • 累计创建 4 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

小游戏:抽奖转盘的实现

实现的方案
  1. CSS3 动画 +JavaScript
  2. Canvas++JavaScript

考虑到性能问题,决定采用 Canvas 方案进行实现。

幸运转盘游戏.html

关键实现类
class LuckyWheel {
            constructor(config) {
                this.canvas = config.canvas;
                this.ctx = this.canvas.getContext('2d');
                this.resultDisplay = config.resultDisplay;
                this.historyList = config.historyList;
                this.spinBtn = config.spinBtn;
                this.resetConfigBtn = config.resetConfigBtn;
                this.defaultPrizes = config.defaultPrizes;
                
                this.prizes = JSON.parse(JSON.stringify(this.defaultPrizes));
                this.isSpinning = false;
                this.rotation = 0;
                this.currentPrizeIndex = -1;
                
                this.centerX = this.canvas.width / 2;
                this.centerY = this.canvas.height / 2;
                this.radius = Math.min(this.centerX, this.centerY) - 10;
                
                this.init();
            }
            
            init() {
                this.drawWheel();
                this.bindEvents();
            }
            
            
            drawWheel() {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                const numPrizes = this.prizes.length;
                const angleStep = (2 * Math.PI) / numPrizes;
                
                for (let i = 0; i < numPrizes; i++) {
                    const startAngle = i * angleStep + this.rotation - Math.PI/2 - angleStep/2;
                    const endAngle = (i + 1) * angleStep + this.rotation - Math.PI/2 - angleStep/2;
                    
                    this.ctx.beginPath();
                    this.ctx.moveTo(this.centerX, this.centerY);
                    this.ctx.arc(this.centerX, this.centerY, this.radius, startAngle, endAngle);
                    this.ctx.closePath();
                    this.ctx.fillStyle = this.prizes[i].color;
                    this.ctx.fill();
                    this.ctx.strokeStyle = '#fff';
                    this.ctx.lineWidth = 2;
                    this.ctx.stroke();
                    
                    this.ctx.save();
                    this.ctx.translate(this.centerX, this.centerY);
                    this.ctx.rotate(startAngle + angleStep / 2);
                    this.ctx.textAlign = 'right';
                    this.ctx.fillStyle = '#fff';
                    this.ctx.font = 'bold 18px "Microsoft YaHei"';
                    this.ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
                    this.ctx.shadowBlur = 3;
                    this.ctx.fillText(this.prizes[i].name, this.radius - 20, 8);
                    this.ctx.restore();
                }
                
                this.ctx.beginPath();
                this.ctx.arc(this.centerX, this.centerY, 20, 0, 2 * Math.PI);
                this.ctx.fillStyle = '#2c3e50';
                this.ctx.fill();
                this.ctx.strokeStyle = '#fff';
                this.ctx.lineWidth = 4;
                this.ctx.stroke();
                
                this.ctx.beginPath();
                this.ctx.arc(this.centerX, this.centerY, 10, 0, 2 * Math.PI);
                this.ctx.fillStyle = '#FFD700';
                this.ctx.fill();
            }
            
            spin() {
                if (this.isSpinning) return;
                
                this.isSpinning = true;
                this.spinBtn.disabled = true;
                this.resultDisplay.textContent = "旋转中...";
                this.resultDisplay.classList.remove('winner');
                
                const spins = 5 + Math.random() * 3;
                const extraDeg = Math.random() * 360;
                const totalRotationDeg = spins * 360 + extraDeg;
                const duration = 4000 + Math.random() * 2000;
                
                const normalizedDeg = (360 - (totalRotationDeg % 360)) % 360;
                const prizeAngleDeg = 360 / this.prizes.length;
                const adjustedDeg = (normalizedDeg + prizeAngleDeg/2) % 360;
                this.currentPrizeIndex = Math.floor(adjustedDeg / prizeAngleDeg);
                
                if (this.currentPrizeIndex >= this.prizes.length) this.currentPrizeIndex = 0;
                
                const startTime = Date.now();
                const animate = () => {
                    const now = Date.now();
                    const elapsed = now - startTime;
                    let progress = elapsed / duration;
                    progress = Math.min(progress, 1);
                    
                    const easeOut = 1 - Math.pow(1 - progress, 3);
                    this.rotation = (easeOut * totalRotationDeg * Math.PI) / 180;
                    
                    this.drawWheel();
                    
                    if (progress < 1) {
                        requestAnimationFrame(animate);
                    } else {
                        this.finishSpin();
                    }
                };
                
                requestAnimationFrame(animate);
            }
            
            finishSpin() {
                this.isSpinning = false;
                this.spinBtn.disabled = false;
                const prizeName = this.prizes[this.currentPrizeIndex].name;
                this.resultDisplay.textContent = `恭喜获得:${prizeName}!`;
                this.resultDisplay.classList.add('winner');
                
                const historyItem = document.createElement('li');
                const now = new Date();
                const timeStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`;
                historyItem.textContent = `[${timeStr}] ${prizeName}`;
                
                if (this.historyList.firstChild) {
                    this.historyList.insertBefore(historyItem, this.historyList.firstChild);
                } else {
                    this.historyList.appendChild(historyItem);
                }
                
                while (this.historyList.children.length > 10) {
                    this.historyList.removeChild(this.historyList.lastChild);
                }
            }
            
            resetConfig() {
                if (this.isSpinning) return;
                
                if (confirm('确定要重置为默认奖品配置吗?当前配置将会丢失。')) {
                    this.prizes = JSON.parse(JSON.stringify(this.defaultPrizes));
                    this.rotation = 0;
                    this.initPrizeConfigUI();
                    this.drawWheel();
                    this.resultDisplay.textContent = "配置已重置,等待旋转...";
                    this.resultDisplay.classList.remove('winner');
                    this.historyList.innerHTML = '';
                }
            }
            
            bindEvents() {
                this.spinBtn.addEventListener('click', () => this.spin());
                this.resetConfigBtn.addEventListener('click', () => this.resetConfig());
                this.canvas.addEventListener('click', () => this.spin());
            }
        }
初始化

需要注意的是,初始化必须在 dom 树加载完毕后执行,否则获取元素可能为空,可以在 script 标签加 defer 标记,或者放在 body 下方,以保证 dom 加载完成。

        // 初始化应用
        const wheel = new LuckyWheel({
            defaultPrizes: [
                { name: "一等奖", color: "#FF6B6B" },
                { name: "谢谢参与", color: "#4ECDC4" },
                { name: "二等奖", color: "#FFD166" },
                { name: "优惠券", color: "#06D6A0" },
                { name: "三等奖", color: "#118AB2" },
                { name: "小礼品", color: "#EF476F" },
                { name: "幸运奖", color: "#9D4EDD" },
                { name: "纪念品", color: "#FF9E6D" }
            ],
            canvas: document.getElementById('wheelCanvas'),
            resultDisplay: document.getElementById('resultDisplay'),
            historyList: document.getElementById('historyList'),
            spinBtn: document.getElementById('spinBtn'),
            resetConfigBtn: document.getElementById('resetConfigBtn')
        });
        
        // 将实例挂载到window以便调试
        window.luckyWheel = wheel;
后续优化

后续还可以对 this.currentPrizeIndex = Math.floor(adjustedDeg / prizeAngleDeg);进行修改,加入接口调用获取抽奖结果。

0

评论区