抽奖必须用确定性随机逻辑:先预生成targetindex并固化,再驱动ui动画逼近;禁用实时重抽、需服务端校验、乱序用fisher-yates、加权抽奖须按概率区间映射。

HTML 本身不提供随机数能力,抽奖逻辑完全由 JavaScript 的 Math.random() 驱动,但「怎么用」直接决定公平性、可复现性和防作弊效果——不是写个 Math.random() 就算完事。
为什么不能直接用 Math.random() 取奖品下标
常见错误是这样写:prizes[Math.floor(Math.random() * prizes.length)]。表面看没问题,但实际埋了三个坑:
- 浏览器在高频率调用(比如每 50ms 一次滚动抽奖)时,
Math.random()在极短时间内可能生成高度相似的浮点数,尤其在旧版 Chrome 或低端设备上,导致「连中同一奖」或「跳过某些奖」 - 没做种子隔离:如果页面里其他逻辑也密集调用
Math.random()(比如动画、日志采样),会污染随机序列,让抽奖结果出现隐性偏移 - 无法回溯验证:一旦用户质疑“我明明看到指针经过一等奖却停在谢谢惠顾”,你拿不出可复现的随机路径来对账
真正可用的抽奖随机逻辑长什么样
核心原则是:**把随机行为和 UI 动画解耦,用确定性算法控制终点**。典型做法是预生成一个「落点」,再让动画朝它逼近:
- 点击开始时,立刻执行一次
const targetIndex = Math.floor(Math.random() * prizes.length),存进闭包或变量,不再重算 - 后续所有定时器里的滚动,只是视觉模拟——比如每帧高亮下一个
div,但不重新抽,只计数i++,直到i === targetIndex - 如果需要支持“多轮不重复中奖”,就用
prizes.splice(targetIndex, 1)移除已中奖品,而不是靠运气跳过 - 若需服务端校验(比如防止前端篡改),必须把这次生成的
targetIndex和时间戳一起发到后端,由服务端比对奖池状态并落库
Math.random() 在抽奖里最常被误用的三个场景
不是所有“看起来随机”的地方都该用它:
-
乱序名单:别用循环 +
Math.random()交换——用Array.sort(() => Math.random() - 0.5)有偏差,正确做法是 Fisher-Yates 洗牌,或直接引入_.shuffle() -
概率加权抽奖:比如一等奖 1%、谢谢惠顾 90%,不能靠
if (Math.random() 堆条件,应构建累积概率数组,再用二分查找匹配落点 -
暂停/继续抽奖:如果用户点了“暂停”再“继续”,不能接着原来定时器跑,而要重新生成
targetIndex,否则等于暴露了原始随机值
真正难的从来不是生成一个随机数,而是让这个数在 UI 动画、用户操作、网络请求、奖池状态之间保持一致且可解释——多数线上抽奖翻车,都卡在没守住这条线。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











