九宫格抽奖思路与实现

46 阅读5分钟
  1. 高亮当前奖项,清除其他奖项高亮样式,function highlightNext()
  2. 转圈先快后慢,通过settimeout(highlightNext(),延时时间) 的 延时时间控制,这里递归调用 function highlightNext(),出口是指定的步数
  3. 作弊指定目标奖项,最终停到某个奖项

效果图:

屏幕截图 2025-09-26 153351.png

屏幕截图 2025-09-26 153356.png

详细代码:

<!DOCTYPE html>

<html lang="zh-CN">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>移动云盘 - 幸运抽奖</title>

    <style>

        * {

            margin: 0;

            padding: 0;

            box-sizing: border-box;

            font-family: "Microsoft YaHei", sans-serif;

        }

       

        body {

            background: linear-gradient(135deg, #1a5f23, #0d8b58);

            display: flex;

            justify-content: center;

            align-items: center;

            min-height: 100vh;

            padding: 20px;

        }

       

        .container {

            width: 100%;

            max-width: 500px;

            text-align: center;

        }

       

        .header {

            margin-bottom: 30px;

            padding: 15px;

            background: rgba(255, 255, 255, 0.1);

            border-radius: 15px;

            backdrop-filter: blur(5px);

        }

       

        .header h1 {

            color: #fff;

            font-size: 28px;

            margin-bottom: 10px;

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

        }

       

        .header p {

            color: #e0f7e0;

            font-size: 16px;

        }

       

        .lottery-box {

            background: rgba(255, 255, 255, 0.15);

            border-radius: 20px;

            padding: 30px 20px 20px;

            position: relative;

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

            backdrop-filter: blur(10px);

            border: 1px solid rgba(255,255,255,0.2);

        }

       

        .lottery-box::before {

            content: "";

            position: absolute;

            top: -8px;

            left: -8px;

            right: -8px;

            bottom: -8px;

            border: 2px dashed rgba(60, 179, 113, 0.5);

            border-radius: 25px;

            pointer-events: none;

            z-index: -1;

        }

       

        .prize-grid {

            display: grid;

            grid-template-columns: repeat(3, 1fr);

            grid-template-rows: repeat(3, 1fr);

            gap: 15px;

            margin-bottom: 25px;

        }

       

        .prize-item {

            background: rgba(255, 255, 255, 0.9);

            border-radius: 12px;

            padding: 15px 8px;

            display: flex;

            flex-direction: column;

            justify-content: center;

            align-items: center;

            height: 120px;

            box-shadow: 0 4px 8px rgba(0,0,0,0.1);

            transition: all 0.3s ease;

            position: relative;

            overflow: hidden;

            border: 1px solid rgba(46, 139, 87, 0.3);

        }

       

        .prize-item.active {

            background: linear-gradient(145deg, #fff9c4, #ffeb3b);

            box-shadow: 0 0 20px #ffeb3b, 0 0 30px rgba(255, 235, 59, 0.5);

            transform: scale(1.08);

            z-index: 2;

            animation: pulse 1s infinite alternate;

        }

       

        @keyframes pulse {

            from { box-shadow: 0 0 15px #ffeb3b; }

            to { box-shadow: 0 0 30px #ffeb3b; }

        }

       

        .prize-item .prize-name {

            font-size: 15px;

            font-weight: bold;

            color: #2e8b57;

            margin-bottom: 6px;

        }

       

        .prize-item .prize-desc {

            font-size: 12px;

            color: #666;

        }

       

        .draw-btn {

            /* grid-column: 2;

            grid-row: 2;

            background: linear-gradient(145deg, #ff5252, #ff0000);

            color: white;

            border: none;

            border-radius: 50%;

            height: 100%;

            display: flex;

            justify-content: center;

            align-items: center;

            font-size: 28px;

            font-weight: bold;

            cursor: pointer;

            box-shadow: 0 6px 12px rgba(0,0,0,0.25);

            transition: all 0.3s ease; */

        }

       

        .draw-btn:hover {

            transform: scale(1.08);

            box-shadow: 0 8px 16px rgba(0,0,0,0.3);

        }

       

        .draw-btn:disabled {

            background: linear-gradient(145deg, #cccccc, #999999);

            cursor: not-allowed;

            transform: scale(1);

        }

       

        .chances-left {

            margin-top: 15px;

            font-size: 16px;

            color: #ffeb3b;

            font-weight: bold;

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

        }

       

        .result-modal {

            display: none;

            position: fixed;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            background-color: rgba(0,0,0,0.8);

            justify-content: center;

            align-items: center;

            z-index: 100;

        }

       

        .result-content {

            background: linear-gradient(145deg, #ffffff, #f0f8f0);

            padding: 35px;

            border-radius: 20px;

            text-align: center;

            max-width: 85%;

            width: 400px;

            animation: popIn 0.6s ease;

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

            position: relative;

            overflow: hidden;

            border: 2px solid #ffeb3b;

        }

       

        .result-content::before {

            content: "";

            position: absolute;

            top: 0;

            left: 0;

            right: 0;

            height: 8px;

            background: linear-gradient(90deg, #ff5252, #ffeb3b, #2e8b57);

        }

       

        @keyframes popIn {

            0% { transform: scale(0.5); opacity: 0; }

            70% { transform: scale(1.1); }

            100% { transform: scale(1); opacity: 1; }

        }

       

        .result-content h2 {

            color: #2e8b57;

            margin-bottom: 20px;

            font-size: 28px;

        }

       

        .result-content p {

            margin: 15px 0;

            font-size: 18px;

            color: #333;

        }

       

        .result-content .prize-won {

            font-size: 24px;

            font-weight: bold;

            color: #ff5252;

            margin: 20px 0;

            padding: 15px;

            background: rgba(255, 235, 59, 0.2);

            border-radius: 10px;

            border: 2px dashed #ff5252;

        }

       

        .close-btn {

            background: linear-gradient(145deg, #2e8b57, #1a5f23);

            color: white;

            border: none;

            padding: 12px 30px;

            border-radius: 50px;

            cursor: pointer;

            margin-top: 20px;

            font-size: 18px;

            font-weight: bold;

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

            transition: all 0.3s ease;

        }

       

        .close-btn:hover {

            transform: translateY(-3px);

            box-shadow: 0 6px 12px rgba(0,0,0,0.3);

        }

       

        .music-icon {

            position: absolute;

            top: 8px;

            right: 8px;

            font-size: 14px;

            color: #ff6b6b;

            font-weight: bold;

            background: rgba(255, 107, 107, 0.1);

            padding: 2px 6px;

            border-radius: 10px;

        }

       

        .cloud-icon {

            position: absolute;

            top: 8px;

            left: 8px;

            font-size: 14px;

            color: #42a5f5;

            font-weight: bold;

            background: rgba(66, 165, 245, 0.1);

            padding: 2px 6px;

            border-radius: 10px;

        }

       

        .pointer {

            position: absolute;

            top: -20px;

            left: 50%;

            transform: translateX(-50%);

            width: 0;

            height: 0;

            border-left: 15px solid transparent;

            border-right: 15px solid transparent;

            border-top: 25px solid #ff5252;

            z-index: 5;

            filter: drop-shadow(0 3px 5px rgba(0,0,0,0.3));

        }

       

        .instructions {

            margin-top: 25px;

            color: #e0f7e0;

            font-size: 14px;

            background: rgba(0,0,0,0.15);

            padding: 10px;

            border-radius: 10px;

        }

    </style>

</head>

<body>

    <div class="container">

        <div class="header">

            <h1>移动云盘幸运抽奖</h1>

            <p>点击抽奖按钮,赢取丰厚奖品!</p>

        </div>

       

        <div class="lottery-box">

            <div class="pointer"></div>

            <div class="prize-grid">

                 <!-- 0 -->

                <div class="prize-item">

                    <div class="cloud-icon"></div>

                    <div class="prize-name">移动云盘</div>

                    <div class="prize-desc">白银会员季卡</div>

                </div>

                <!-- 1 -->

                <div class="prize-item">

                    <div class="prize-name">100M流量日包</div>

                </div>

                <!-- 2 -->

                <div class="prize-item">

                    <div class="music-icon">Music</div>

                    <div class="prize-name">咪咕音乐</div>

                    <div class="prize-desc">30GB定向流量</div>

                </div>

                <!-- 3 -->

                <div class="prize-item">

                    <div class="prize-name">2GB流量日包</div>

                </div>

                <!-- 4 -->

                 <div class="prize-item draw-btn" id="drawBtn">

                    <!-- <button class="draw-btn" id="drawBtn">抽</button> -->

                     <p class="draw-btn-title"></p>

                     <p class="remain-text" id="chancesLeft">剩余2次机会</p>

                 </div>

               

                <!-- 5 -->

                <div class="prize-item">

                    <div class="prize-name">4GB流量日包</div>

                </div>

                <!-- 6 -->

                <div class="prize-item">

                    <div class="prize-name">1GB流量日包</div>

                </div>

                <!-- 7 -->

                <div class="prize-item">

                    <div class="prize-name">500M流量日包</div>

                </div>

                <!-- 8 -->

                <div class="prize-item">

                    <div class="prize-name">谢谢参与</div>

                </div>

            </div>

            </div>

           

            <div class="chances-left" id="chancesLeft">剩余3次机会</div>

        </div>

       

        <!-- <div class="instructions">

            提示:抽奖结果由指针停止位置决定,停止在哪个奖品上就获得哪个奖品

        </div> -->

    </div>

   

    <div class="result-modal" id="resultModal">

        <div class="result-content">

            <h2>抽奖结果</h2>

            <p id="resultText">恭喜您获得:</p>

            <div class="prize-won" id="prizeWon"></div>

            <p id="chancesAfter">剩余2次机会</p>

            <button class="close-btn" id="closeBtn">确定</button>

        </div>

    </div>

  


    <script>

        document.addEventListener('DOMContentLoaded', function() {

            const drawBtn = document.getElementById('drawBtn');

            const chancesLeft = document.getElementById('chancesLeft');

            const resultModal = document.getElementById('resultModal');

            const resultText = document.getElementById('resultText');

            const prizeWon = document.getElementById('prizeWon');

            const chancesAfter = document.getElementById('chancesAfter');

            const closeBtn = document.getElementById('closeBtn');

            const prizeItems = document.querySelectorAll('.prize-item');

           

            let chances = 3;

            let isDrawing = false;

            let currentPosition = 0; // 当前高亮位置

           

            // 奖品列表(与DOM中的奖品顺序一致)

            const prizes = [

                "移动云盘白银会员季卡",

                "100M流量日包",

                "咪咕音乐30GB定向流量",

                "2GB流量日包",

                "哈哈",

                "4GB流量日包",

                "1GB流量日包",

                "500M流量日包",

                "谢谢参与"

            ];

           

            // 修复:正确的顺时针顺序索引(基于DOM中的实际位置)

            // 左上(0) -> 中上(1) -> 右上(2)

            // -> 右中(5) -> 右下(8)

            // -> 中下(7) -> 左下(6)

            // -> 左中(3)

  
  


           

            /***

             

             0 1 2

             3 4 5

             6 7 8

  
  


             */

           

            // 对应prizeItems索引: [0, 1, 2, 4, 7, 6, 5, 3]

            const clockwiseOrder = [0, 1, 2, 5, 8, 7, 6, 3];

           

            // 更新剩余次数显示

            function updateChances() {

                chancesLeft.textContent = `剩余${chances}次机会`;

                if (chances === 0) {

                    drawBtn.disabled = true;

                    drawBtn.textContent = "结束";

                }

            }

           

            // 抽奖函数 (最终停止的位置 跟 rounds有关 所以我让 rounds等于40+ 0-8的随机数。)

            function drawPrize() {

                if (isDrawing || chances <= 0) return;

               

                isDrawing = true;

                chances--;

                updateChances();

                drawBtn.disabled = true;

               

                // let rounds = 40  //步数 40步,8个奖项,算转5圈

                // let rounds = 40 + Math.floor(Math.random() * 8); // 旋转圈数

                let rounds = 40 + 8 // + 多少就能得到对应的奖品(可作弊获得指定奖品)clockwiseOrder[8]  是节点为3的奖品,顺时针推

                let speed = 50; // 初始速度

               

                // 高亮动画函数

                function highlightNext() {

                    // 移除所有高亮

                    prizeItems.forEach(item => item.classList.remove('active'));

                   

                    // 获取当前步骤对应的DOM索引

                    const currentIndex = clockwiseOrder[currentPosition];

                   

                    // 高亮当前项(确保索引在有效范围内)

                    if (currentIndex >= 0 && currentIndex < prizeItems.length) {

                        prizeItems[currentIndex].classList.add('active');

                    }

                    rounds--;

                   

                   

                   

                   

                    // 优化减速逻辑

                    if (rounds > 30) {

                        // 前10步快速旋转

                        speed = 50;

                    } else if (rounds > 20) {

                        // 开始减速

                        speed = 100;

                    } else if (rounds > 10) {

                        // 进一步减速

                        speed = 200;

                    } else if (rounds > 5) {

                        // 明显减速

                        speed = 300;

                    } else {

                        // 最后几步非常慢

                        speed = 400;

                    }

                   

                    // 继续动画或停止

                    if (rounds > 0) {

  


                        // 移动到下一个位置

                        currentPosition = (currentPosition + 1) % clockwiseOrder.length;

  


                        setTimeout(highlightNext, speed);

                        console.log(currentPosition);

  


                    } else {

                         console.log(currentPosition);

                        // 确保最终停在当前位置

                        prizeItems.forEach(item => item.classList.remove('active'));

 

                       

                        // 找到获奖项对应的DOM索引

                        let winningDomIndex = clockwiseOrder[currentPosition];

                       

                       

                        if (winningDomIndex >= 0 && winningDomIndex < prizeItems.length) {

                            prizeItems[winningDomIndex].classList.add('active');

                        }

                       

                        // 显示结果

                        setTimeout(showResult, 800, winningDomIndex);

  


                        currentPosition = 0

                    }

                }

               

                // 开始动画

                highlightNext();

            }

           

            // 显示抽奖结果

            function showResult(prizeIndex) {

                // 确保索引在有效范围内

                if (prizeIndex < 0 || prizeIndex >= prizes.length) {

                    prizeIndex = 0; // 默认第一个奖品

                }

               

                const prize = prizes[prizeIndex];

                prizeWon.textContent = prize;

               

                if (prize === "谢谢参与") {

                    resultText.textContent = "很遗憾,您未中奖";

                    prizeWon.textContent = "";

                } else {

                    resultText.textContent = "恭喜您获得:";

                }

               

                chancesAfter.textContent = `剩余${chances}次机会`;

                resultModal.style.display = "flex";

                isDrawing = false;

               

                if (chances > 0) {

                    drawBtn.disabled = false;

                }

            }

           

            // 关闭结果弹窗

            function closeModal() {

                resultModal.style.display = "none";

                prizeItems.forEach(item => item.classList.remove('active'));

            }

           

            // 事件监听

            drawBtn.addEventListener('click', drawPrize);

            closeBtn.addEventListener('click', closeModal);

           

            // 初始化

            updateChances();

        });

    </script>

</body>

</html>