把定时器、防叠加、概率控制全串起来。核心就五件事:① 8 格顺时针高亮;② 先加速后减速;③ 点击瞬间就随机定好结果;④ 权重控制中奖概率;⑤ disabled + timer 双重防连点。
// 1. 摆布局:8 个奖品格子按顺时针排一圈,中间放抽奖按钮 // 都用绝对定位;取 div 的顺序必须跟你眼睛看到的顺时针一致 // 2. 让它转:用一个下标记现在亮着哪格,定时器一格一格切 // 3. 怎么变速:setInterval 定下来后间隔不能直接改 // → 只能每圈"把旧闹钟砸了 → 换个速度 → 再定个新闹钟" // 前半圈间隔越压越小(越转越快) // 后半圈间隔越拉越大(慢慢停下来) // 4. 怎么停:你点下去的瞬间就随机定好停哪格,算好一共走几步 // 步数走完就停(真实项目是后端先把结果告诉你,前端再停) // 5. 防 bug:转起来时把按钮锁死(disabled + 看 timer 双保险) // 走到终点一定要 return,不然会多走一格
// ====== 先把要用的家当摆好 ======
// 中间那个抽奖按钮
const btn = document.getElementsByClassName("button")[0];
// 整个九宫格的外框
const box = document.getElementsByClassName("box")[0];
// 把 8 个奖品格子全取下来,顺序正好是 HTML 里从上到下、也就是顺时针
const prizeDomList = box.getElementsByTagName("div");
// 闹钟编号,null 表示现在没在转(防叠加关键)
let timer = null;
// 现在亮着哪一格,从第 0 格开始
let index = 0;
// 一开始每 200 毫秒亮一下
let speed = 200;
// 最快也不能快过 80 毫秒,封顶
const minSpeed = 80;
// 最后要停在哪一格
let targetIndex = 0;
// 至少要先完整转 4 圈再停
const baseLoop = 4;
// 一共 8 格
const totalNum = prizeDomList.length;
// ====== 点按钮就启动 ======
function startLottery() {
// 拦截 1:已经在转了,再点也没用,直接走人
if (timer) return;
// 拦截 2:把按钮按灰,不让连点
btn.disabled = true;
// 从头开始亮
index = 0;
// 速度回初始值
speed = 200;
// 这一刻就随机定好停哪格
targetIndex = Math.floor(Math.random() * totalNum);
// 算好总共要走几步
let remainStep = baseLoop * totalNum + targetIndex;
// 比如 4 圈 × 8 格 + 目标格 = 正好停在中奖格
...
}
// 1. 先把所有格子的高亮都关掉,再只亮当前这一格
function rotate() {
for (let i = 0; i < totalNum; i++) prizeDomList[i].classList.remove("active");
prizeDomList[index].classList.add("active");
// 2. 步数走完了吗?走完就该停了
if (remainStep <= 0) {
// 把闹钟砸了
clearInterval(timer);
timer = null;
// 按钮恢复可点
btn.disabled = false;
alert("恭喜您获得:" + prizeDomList[index].innerHTML);
// ★ 必须在这 return,不然会多走一格!
return;
}
// 还没走完,步数减一
remainStep--;
// 3. 变速:前半段越跑越快,后半段慢慢减速
if (remainStep > (baseLoop * totalNum) / 2) {
// 间隔变小 = 转得更快
speed -= 8;
// 快到顶就别再快了
if (speed < minSpeed) speed = minSpeed;
} else {
// 间隔变大 = 慢慢减速
speed += 12;
}
// 4. 高亮格子往下挪一格,到第 8 格就回到第 0 格
index++;
if (index >= totalNum) index = 0;
// 5. 把旧闹钟砸了,按新速度再定一个(这就是变速的写法)
clearInterval(timer);
timer = setInterval(rotate, speed);
}
四个坑点逐个记:① 终点判断后必须 return;② 变速 = 销毁重建定时器(间隔不能改);③ disabled + timer 双重防连点;④ 点击瞬间提前随机定结果,不停下才随机。
想让"一等奖"难中、"谢谢参与"好中,就用权重:权重数字 = 这个奖在"命运转盘"上占的份数。
const prizeConfig = [
// 一等奖只占 5 份,难中
{ name: "一等奖", weight: 5 },
// 二等奖占 15 份
{ name: "二等奖", weight: 15 },
// 三等奖占 20 份
{ name: "三等奖", weight: 20 },
// 幸运奖占 20 份
{ name: "幸运奖", weight: 20 },
// 谢谢参与占 30 份,最多最好中
{ name: "谢谢参与", weight: 30 },
{ name: "再来一次", weight: 5 },
{ name: "红包 5 元", weight: 3 },
{ name: "优惠券", weight: 2 }
];
// 把所有份数加起来 = 5+15+20+20+30+5+3+2 = 100
// 随机扔一个 0~99 的数,落在哪个奖的地盘上,就中哪个
// 一等奖占 [0,5)、二等奖占 [5,20)……谢谢参与占 [55,85),地盘最宽
| 步骤 | 做法 |
|---|---|
| 1 | 计算所有奖品权重总和 |
| 2 | 生成 0 ~ 总权重 之间的随机数 |
| 3 | 遍历权重数组累加,随机数落在哪个区间,对应奖品就是中奖结果 |
权重越大 = 区间越宽 = 越容易中。真实项目里结果由后端接口返回(防止前端作弊),前端等结果再停止转盘。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| setInterval(函数, 毫秒) | 转盘一格一格动起来 | 函数、间隔毫秒 | ID | let t = setInterval(function () { 转一格 }, 50); |
| clearInterval(ID) | 砸掉旧闹钟,换新速度 | 定时器 ID | 无 | clearInterval(t); // 砸掉旧闹钟换速度 |
| classList.add / remove(类名) | 高亮当前格、去掉旧高亮 | CSS 类名 | 无 | item.classList.add('active'); item.classList.remove('active'); |
| 按钮.disabled = true/false | 抽奖中锁按钮,防连点 | true / false | 无 | btn.disabled = true; // 抽奖中锁按钮 |
| Math.random() | 点击瞬间随机定中奖结果 | 无 | 0~1 之间随机数 | let n = Math.floor(Math.random() * 6); // 0~5 |