年会抽奖怕暗箱:写了个随机抽签工具,保证公平
【摘要】 纯前端随机抽签,自己输入名单一键随机抽取,支持滚动动画。技术内核是 Fisher-Yates 洗牌算法——保证每人被抽中的概率完全相等且不重复。
部门抽奖,用 Excel 点名总有人嘀咕"是不是内定"。我自己写个网页抽签:名单自己填,点一下随机出一个,关键是算法得真随机、不重复。简单 Math.random() 取一个其实有隐患,正经该用洗牌算法。
一、为什么不直接 random 取一个
最朴素的写法:
function pick(list) {
return list[Math.floor(Math.random() * list.length)];
}
抽一个人还行。但要连续抽、且不重复(比如抽三等奖 3 人),这种写法会"同一个人被抽两次"——因为每次都从全集里取,不剔除已中的。
二、Fisher-Yates 洗牌
正确做法先把名单打乱,再按顺序取,这样每个人只会出现一次,且位置完全随机。
function shuffle(arr) {
const a = [...arr]; // 复制,不改原名单
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 0~i 随机位
[a[i], a[j]] = [a[j], a[i]]; // 交换
}
return a;
}
function draw(list, n = 1) {
return shuffle(list).slice(0, n); // 取前 n 个
}
从后往前,每一步把当前位和前面随机一位交换。数学上能保证每个排列等概率,比"随机取再判断有没有抽过"的写法更干净,也不会陷入重复抽取死循环。
三、滚动动画

视觉上让名字快速闪动再停,制造"抽奖感":
function roll(list, ms = 1500) {
const box = document.getElementById('result');
const t0 = Date.now();
const id = setInterval(() => {
box.textContent = list[Math.floor(Math.random() * list.length)];
if (Date.now() - t0 > ms) {
clearInterval(id);
box.textContent = draw(list, 1)[0]; // 停下时给真结果
}
}, 60);
}
动画只是表演,最终停下的结果用 draw() 算,跟闪烁无关。
四、几种抽取方式对比
| 方式 | 能否不重复 | 公平性 | 适用 |
|---|---|---|---|
| random 取一个 | 否 | 单抽公平 | 只抽 1 人 |
| 标记已抽再 random | 能,但可能循环 | 公平 | 人多时慢 |
| Fisher-Yates | 能 | 严格等概率 | 任意场景 |
小结
抽签别用 Math.random() 反复取一个——连续抽多人会重复命中。正经用 Fisher-Yates 洗牌:从后往前每步与前面随机位交换,数学上保证每个排列等概率、每人只出现一次。复制原数组洗牌避免破坏名单。滚动动画只是表演,最终停在用洗牌结果算出的真值上,跟闪烁无关。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)