
简介这份HTML5现场抽奖活动源码专为公司年会、婚礼庆典、朋友聚会等线下活动量身打造可快速配置名单、奖项、中奖人数及每轮抽取人数并根据不同主题替换文字与特效省去从零开发抽奖页面的时间。资源包共31个文件大小约178.71MB其中HTML、CSS与JS文件构成抽奖页面及交互逻辑PNG、JPG图片用于界面背景和奖品图MP3、MP4音视频承担背景音乐与抽奖效果演示TXT说明文档帮助快速上手。目前已有992人浏览学习适合活动策划、主持人以及需要落地抽奖功能的前端初学者参考。包内附带预览图与演示视频可直观看到抽奖及中奖动画效果代码独立、结构清晰既能在浏览器中直接打开使用也支持在原有基础上扩展定制满足各类聚会和年会现场氛围需求。 前年帮朋友婚礼做现场抽奖第一版方案我直接用了网上的在线抽奖H5结果现场WiFi被几十号人一挤大屏上的转盘卡成了PPT新人脸都绿了。从那次之后我就学乖了——所有现场抽奖类的活动我全部改用本地HTML5单文件方案不依赖网络、不依赖服务器、双击就能跑这次把从需求分析到成品源码的完整过程整理出来给婚庆主持、公司行政、聚会组织者做个参考。简单说这个源码就是一个纯前端实现的单页应用把人员名单加载进去、点一下开始滚动、再点一下停止出结果支持多轮抽取自动排除已中奖者还配了大屏滚动特效和音效。它解决的痛点很明确现场抽奖最怕抽重、名单临时变动、彩排时数据改来改去以及最致命的——活动进行到一半网络抽风。整套东西适合拿来即用改改文字就上场的程序员也适合非技术背景的策划人员直接照说明操作。1. 需求分析与方案选型1.1 现场抽奖场景的“隐性需求”很多人以为现场抽奖就是“随机出个名字”真到活动策划阶段才会发现需求远不止这么简单。综合婚礼、年会、客户答谢会这些场景抽奖功能至少要拆成四层第一层是名单管理。现实中的名单不是一份完美的Excel可能是微信接龙、纸质签到表、临时补录的VIP客人。所以源码不能只支持一种固定格式最好支持手工粘贴和TXT文件导入两套入口且能容忍一行一个名字这类最朴素的格式。第二层是抽奖公平性与可预期性。纯随机看似公平实际执行时需要根据奖项等级控制节奏——三等奖多抽几组营造氛围、特等奖稍微放慢滚动速度直到观众喊停。因此算法不能只是一个“抽一次出一个结果”的黑盒子滚动过程本身也是演出的一部分。第三层是结果管理与防错。连续多轮抽奖最怕人已中奖又进下一轮也怕主持人念错名字。这就需要在规则上限定“已中奖者自动排除”并在界面上把最近三到五轮的中奖名单常驻显示方便主持人和后台人员随时核对。第四层是现场可运维性。会场设备五花八门有的用主办方笔记本接大屏有的用现场的触控一体机但凡要装环境、装运行库的方案都有翻车风险。一个双击即开的HTML文件只要设备有浏览器就能跑这几乎是现场工具的最优解。1.2 为什么选择本地HTML5而不是小程序或App我见过不少团队用小程序做年会抽奖体验其实不差但有两个绕不开的问题一是小程序必须依赖平台方审核和数据通道活动当天如果接口限额或服务器异常你除了干等没有别的办法二是抽奖名单往往涉及参与者手机号、部门、工号等信息走第三方平台意味着这些隐私数据全部过了一遍别人的服务器对很多公司来说这是合规层面的忌讳。本地HTML5方案把这两件事同时解决了。数据全程只在现场这台电脑里流转名单既可以提前写入也可以现场临时改甚至断网状态丝毫不受影响。浏览器本身就是天然的跨平台运行环境Windows的Chrome、macOS的Safari、甚至Linux下的Firefox都能跑只要不是古董级浏览器基本没有兼容压力。技术上还有一个隐性优势单HTML文件便于传输和备份。我用U盘拷一份、网盘放一份、邮箱发一份到现场哪怕设备临时更换只要把文件拖过去打开就恢复原样没有任何部署成本。对比一下你需要安装App、注册账号的项目光想到现场出问题时没人知道怎么退出全屏就已经够劝退了。1.3 技术栈构成与关键依赖这个项目刻意回避了所有需要联网加载的外部依赖。CSS动画、随机算法、音效播放全部走原生浏览器能力核心就三块HTML Canvas负责粒子和大屏特效CSS3负责转场和抖动动画JavaScript负责名单解析、抽奖逻辑和本地存储。唯一可选的外部资源是背景音乐和提示音文件但我也预留了降级方案——如果现场不方便放音频文件可以直接用Web Audio API合成简单的音效。字体处理上有个细节值得单独说。活动现场大屏通常分辨率在1080P以上系统自带的微软雅黑在远距离看会显得很细推荐用思源黑体或者站酷高端黑这类粗壮的免费商用字体。不过字体文件动辄几MB直接内嵌会让HTML体积激增实际上我采用的是CSS的font-face外链方案把字体文件放在同目录的fonts文件夹下这样既能保证显示效果又不至于让单文件变得臃肿。2. 核心功能详解与界面设计2.1 功能模块拆解这个抽奖工具我拆成了五个功能模块每个模块应对一个实战场景。名单管理模块负责读取和清洗名单。我做了两个入口一是文本框直接粘贴二是选择TXT文件。粘贴方式适合临时加人比如新人突然说“把我表妹加进去”你复制名字贴进输入框就行。文件读取则用FileReader接口实现编码默认按UTF-8解析但考虑到Windows记事本默认可能是ANSI编码文件读取失败时会自动回退到GBK解析避免中文乱码。抽奖轮次模块用数字区分奖等比如一级奖、二级奖、三级奖每轮可以设定抽取人数。这里有个设计细节不同轮次的滚动速度曲线不同三等奖的滚动速度快、画面花哨而终极大奖滚动速度会明显放慢、停顿感更强营造悬念氛围。历史记录模块把每一轮抽中的名单存进localStorage同时显示在页面右侧或底部的滚动条里。现场主持人经常要回头确认“上一轮中的是哪几位”有这个常驻记录就省去了翻手机的尴尬。屏幕适配模块解决了“电脑上看着正常、投到大屏上字太小”的老问题。我干脆不做响应式适配而是让用户在大屏模式下直接调整整体缩放比例默认按1920×1080设计碰到4K大屏可以通过快捷键调大字号。音效控制模块支持开关背景音乐和抽奖音效音量独立调节。现场的工程人员基本不在乎花哨功能他们最想要的是一个“一键静音”键——万一音效和主持人话语重叠出戏了点一下立刻安静。2.2 界面布局与视觉设计思路现场大屏的UI设计和普通网页差别很大。普通网页讲究信息密度大屏则要求“远看三秒能看懂”。我整个页面只保留三个视觉层级最上方是当前奖项名称和抽取人数中部的巨大卡片区域是抽奖名字展示区底部三分之一留给了历轮中奖名单。配色方案上婚礼、年会这类场景普遍喜欢金色和红色系但直接大面积用纯红会显得廉价。我用了深红色作为主背景叠加一层半透明的金色放射状渐变中间的名片区域用纯白和深灰形成高对比保证投影仪亮度不足时也能看清。名字字号在1080P下至少做到120px配合CSS的text-shadow做描边处理这比单纯加粗更容易在远距离辨认。抽奖滚动时的视觉反馈是另一个需要打磨的细节。我参考了老虎机的设计风格名字卡片在滚动过程中不断切换同时叠加细微的随机位移和阴影跳动停顿时加入弹跳动画。这一系列效果全部用CSS3 animation和transition实现没有引入任何动画库性能开销很小老电脑上也不会卡顿。界面布局代码如下div classstage header classstage-header h1 idawardName特等奖/h1 p classsub-title抽取 1 名幸运来宾/p /header main classstage-main div classname-card idcurrentWinner span classname-text等待抽奖/span /div /main footer classstage-footer div classhistory-title中奖记录/div ul idhistoryList/ul /footer /div2.3 名单管理从导入到去重的完整流程名单这块我踩过的坑最多。第一版只支持文本框粘贴结果婚礼前一天新人发来一个Excel的问我要怎么导入——我总不能让人家先转成TXT再复制吧所以第二版我加了文件导入功能读取逻辑用FileReader按行切分每一行当作一个名字支持逗号、Tab、换行三种分隔符基本覆盖了主流名单格式。还有一个容易忽略的点是名单去重。有些名单里同一人出现两次可能是签到表本身有重复行也可能是宾客名单和工作人员名单合并时产生交叉。我在导入时用Set去重同时把重复次数统计出来做个提示“共导入120人发现3个重复项已自动合并。”这个信息现场很有用主持人可以顺口打个趣也避免了“为什么我朋友的名字在榜单上出现了两次”这种低级尴尬。名单导入处理后还需要一个预览环节。我加了一个可折叠的名单预览区域默认只显示前50个人名避免一次性渲染上千条DOM导致页面卡顿。预览区的另一个作用是可以核对是否有顾名、错别字毕竟抽奖抽出一个名字打错了比抽不中更尴尬。function parseNameList(rawText) { const lines rawText.split(/\r?\n|,|\t/); const cleaned []; const seen new Set(); let duplicateCount 0; for (let line of lines) { const name line.trim(); if (!name) continue; if (seen.has(name)) { duplicateCount; continue; } seen.add(name); cleaned.push(name); } return { names: cleaned, duplicateCount }; }3. 核心逻辑与关键代码实现3.1 不重复抽奖的随机算法选择现场抽奖最核心的算法要求只有一条同一轮次内不能重复、所有轮次之间已中奖者不能再中。很多人的第一反应是“每次都随机选一个如果已中奖就重新抽”这其实是错误的实现原因很微妙当剩余人数很少时这种“拒绝采样”方式会陷入极长的循环尤其是只剩最后一人时几乎要无限次随机才能命中。我采用的做法是Fisher-Yates洗牌算法。每一轮开始时先把未中奖名单复制一份并洗牌然后按顺序取前N个作为本轮中奖者。这样做的好处是时间复杂度稳定在O(n)且算法天然保证不重复不存在“抽到自己人”后需要回退重来的逻辑。function shuffleArray(arr) { const result [...arr]; for (let i result.length - 1; i 0; i--) { const j Math.floor(Math.random() * (i 1)); [result[i], result[j]] [result[j], result[i]]; } return result; } function drawWinners(availableNames, count) { const shuffled shuffleArray(availableNames); return shuffled.slice(0, count); }可能有朋友会问既然每轮都要洗牌那为什么不一开始就把所有人的抽奖顺序全部洗好其实这也是我第一版的做法把300人的顺序全部定好三等奖抽前50名、二等奖抽之后30名……表面上看逻辑很简单但现场一旦有嘉宾临时加进来或者主持人说“这轮再补抽一个”整个预排序列全部作废必须从头洗牌。所以不如每一次抽取时都对“还没中过的人”洗牌灵活性和公平性都更有保证。3.2 滚动特效与“停不下来”的节奏控制滚动抽奖最核心的体验是“随机感”和“悬念感”。如果只是简单地每隔0.1秒替换一个名字观众很快会疲惫。我在实现时给滚动过程设计了三个速度阶段启动阶段由慢到快中段保持高速结尾阶段由快转慢并最终停止。这三个阶段通过一个简单的缓动函数控制模拟出老虎机物理滚动的惯性和摩擦感。具体实现方式是维护一个setInterval定时器每帧更新当前显示名字同时用requestAnimationFrame驱动一个时间轴根据时间轴的位置计算当前应该处于哪个速度阶段再动态调整定时器的间隔。这样做的好处是特效的实际表现和代码是解耦的调整节奏不需要动核心逻辑只需要改三四个参数即可。停顿时的弹跳效果用的是一组keyframes让名字卡片在停止瞬间做一个缩放和上下抖动的动作。其实很多人不知道这个弹跳是个障眼法——真正的中奖者名字在停止前0.3秒就已经确定了剩下的0.3秒只是在播放动画而已。这样做是为了防止截止瞬间的偶发卡顿导致显示和算法结果不一致。function startRolling() { const duration 6000; // 滚动总时长 6 秒 const startTime performance.now(); function tick(now) { const elapsed now - startTime; if (elapsed duration) { stopRolling(); return; } const progress elapsed / duration; // 速度曲线前 20% 加速中 60% 高速最后 20% 减速 let interval 80; if (progress 0.2) { interval 200 - progress * 600; } else if (progress 0.8) { interval 50 (progress - 0.8) * 700; } else { interval 50; } updateCurrentName(); setTimeout(() requestAnimationFrame(tick), interval); } requestAnimationFrame(tick); }3.3 中奖名单的持久化与多轮管理localStorage是前端实现数据持久化最简单的方案。我把每一轮中奖结果都存进一个JSON数组里结构大概是{ round: 1, award: 三等奖, winners: [张三, 李四] }。这样做有一个隐藏收益如果现场电脑意外断电重启打开页面后可以恢复之前的中奖记录不会出现“前面白抽了”的事故。多轮管理的核心是维护一个全局的remainingNames数组。每一轮抽取结束后从该数组中剔除本轮中奖者。这里我特意用了“重新过滤”而不是“原地删除”每次抽奖前根据完整名单和已中奖名单动态计算剩余名单避免在操作过程中因为误删导致数据不一致。为了满足“这轮抽完再抽一轮但没人可抽”的边缘情况我加了一个自动判断如果剩余人数小于本轮需要抽取的人数页面会弹出提示并禁用抽奖按钮防止现场出现主持人点了半天结果一个名字都出不来的尴尬。function getRemainingNames() { const allNames state.allNames; const winnerNames new Set( state.history.flatMap(item item.winners) ); return allNames.filter(name !winnerNames.has(name)); } function saveRoundResult(awardName, winners) { state.history.push({ round: state.history.length 1, award: awardName, winners: winners }); localStorage.setItem(lucky-draw-history, JSON.stringify(state.history)); }3.4 音效触发与现场音量控制浏览器对自动播放音频有严格的限制直接调用audio.play()大概率被拦截。我踩过这个坑后采用的方案是在用户第一次点击“开始抽奖”时先创建一个AudioContext并把它恢复为running状态之后所有的音效都通过这个上下文来播放。这样既绕开了自动播放限制又把音效播放延迟控制在可接受范围内。背景音乐和音效我分开处理背景音乐用HTMLAudioElement循环播放音效则用AudioContext生成短促的合成音。现场活动中主持人说话和音乐共存是常态所以页面上放了一个总音量滑条和一个独立音效开关。这个设计其实是从直播软件里学来的——观众端可以根据自己环境调整音量而不是被动接受一个固定值。合成音效的实现也很简单我用振荡器做了一个快速上升的音效用于滚动过程中停止时再播放一个低频锣声用正弦波加指数衰减模拟。不需要任何音频素材文件整个页面依然是单文件可分发状态。4. 部署流程与常见问题排查4.1 从U盘到现场的完整部署流程这套东西的部署流程极简但我在多次实战中总结了一套具体的操作序列照着做基本不会出岔子。第一步准备名单文件。推荐用TXT格式每行一个名字保存时务必选择UTF-8编码。如果名单在Excel里先在Excel中选中姓名列复制后粘贴到记事本再另存为TXT。我这里再次强调编码问题Windows记事本默认保存为ANSIHTML文件解析时大概率乱码必须手动在另存为对话框里把编码选成UTF-8。第二步打开页面导入名单。点击导入按钮选择TXT文件页面会自动解析并显示预览。检查预览中的人名是否完整、是否有乱码确认后点击“确认导入”。第三步设置奖项。默认预置了特等奖、一等奖、二等奖、三等奖可以修改每个奖项的名称和抽取人数。如果现场临时要调整规则可以直接在文本框里改改完点击“保存设置”即可。第四步全屏展示。按F11进入浏览器全屏模式或者用系统自带的投屏功能。如果大屏显示比例不对按或-调整整体缩放比例。建议在彩排时就用现场实际设备试一次全屏效果不要等到正式开始。第五步抽奖操作。点击“开始抽奖”按钮名字开始滚动。滚动过程中再次点击同一按钮此时按钮文字已变为“停止”即完成一次抽取。系统会自动把中奖者从剩余名单中移除并写入历史记录。4.2 高频问题排查速查以下是我在实际活动中遇到过的真实问题整理了对应排查思路问题现象可能原因解决办法导入TXT后中文全是乱码文件编码为ANSI不是UTF-8用记事本另存为并选择UTF-8编码后重新导入点击开始按钮没有反应名单未导入或已全部中完检查页面左下角的剩余人数统计确认剩余人数大于0大屏显示的字太小或布局偏移屏幕长宽比与设计稿不一致按Ctrl或-缩放浏览器页面或使用内置的缩放滑条音效不响浏览器自动播放策略拦截第一次点击页面任意位置后再次尝试或检查音效开关是否开启抽奖过程中页面卡顿名单人数过多或电脑性能较低核实名单人数超过1000人时把历史记录区域折叠或换用Chrome浏览器换了一台电脑打开显示异常浏览器版本过旧或字体缺失更新浏览器到最新版本同时把fonts文件夹和HTML文件放在同一个目录下4.3 实战中的几个独家建议说完排查思路再分享几个从现场总结出来的经验。第一强烈建议准备一台备用设备。很多活动团队只准备了一台电脑接大屏一旦这台电脑中途蓝屏、死机或误触关闭了页面整个抽奖环节就凉了。我的做法是同时在后台准备一台笔记本或平板把同一个HTML文件和名单数据在备用设备上也开好保持页面空白状态、不开启抽奖万一主设备出问题备用设备三秒就能顶上。第二抽奖名单建议保留两份不同编码的副本。一份UTF-8编码用于正常导入一份GBK编码用于应急。别问为什么有一次我到了现场发现U盘里的名单文件不知怎么变成了GBK编码幸好当时带了两份才没翻车。第三大屏特效在正式抽奖前至少完整跑一遍。重点是看滚动速度曲线是否符合现场节奏——如果三等奖抽取时滚动太快观众还没反应过来名字已经出来了现场效果会大打折扣。我一般会把三等奖的滚动时长调到8秒以上让主持人有足够的互动时间。第四针对年会场景中奖名单通常需要同步给后台的颁奖组。我在源码里放了一个“导出中奖名单”按钮点击后生成一个文本文件并触发下载。颁奖组拿到这份名单后可以提前安排证书和奖品不用等主持人念完才匆匆忙忙去翻找。5. 源码结构、二次开发建议与安全发布5.1 文件目录结构与核心模块划分即使你是第一次接触这个项目的原样源码先看懂目录结构也能快速定位要改的地方。标准的目录结构大概是这样lucky-draw/ ├── index.html // 主页面含全部结构、样式和逻辑 ├── fonts/ │ ├── SourceHanSansCN-Bold.otf │ └── license.txt ├── assets/ │ ├── bgm.mp3 // 背景音乐可替换 │ ├── tick.mp3 // 滚动音效 │ └── ding.mp3 // 中奖提示音 ├── README.md // 使用说明 └── sample-names.txt // 示例名单文件主页面内部分为三个区域style里是整套CSS样式body底部有完整的HTML结构script里封装了所有JavaScript逻辑。如果你只想改字号、配色、文案直接在style和对应标签里搜索关键词即可如果要调整抽奖规则重点关注drawWinners和getRemainingNames两个函数。需要说明的是我刻意没有把代码拆分成多个JS文件而是全部内联到单HTML文件中。这样做的原因很实际——现场维护一个文件比维护一个目录树要靠谱得多。你不可能要求婚庆公司的工作人员学会部署静态资源服务器但任何人都会双击打开一个HTML文件。5.2 二次开发中常见的技术注意点很多人拿到源码后第一件事就是改界面结果改完发现抽奖逻辑也不工作了。这里列几个二次开发时最容易踩的坑。一是操作DOM节点时务必确认ID没有写错。整个页面有十几个关键节点比如#currentWinner、#historyList、#startBtn如果你的HTML结构调整了新节点的IDJavaScript里的document.getElementById就会返回null导致点击事件失效。调式时优先打开浏览器控制台F12看有没有红色的报错信息。二是修改随机算法时不要破坏“已中奖者排除”的全局约束。有些朋友想给特定嘉宾“加权重”比如让新人的直系亲属更容易中奖这会涉及改drawWinners函数。我建议不要直接改洗牌逻辑而是在洗牌前先给特定名单加重或轻的权重用repeat方式实现比如把某人加入数组多次这样他在洗牌后出现在前N位的概率自然增大而不影响其他逻辑。三是所有状态数据的读写集中在state对象上。设计之初我特意把名单、历史记录、奖项设置全部挂载到全局唯一的state对象上这样做的初衷是方便未来的功能扩展比如增加“现场扫码实时参与”或“弹幕上墙”等功能时只需要扩展state对象和对应的UI区域不需要改动已有的抽取算法。如果你自己维护代码建议沿用这个习惯。5.3 源码发布与使用边界关于源码发布我补充几个合规层面的经验。如果要在公开渠道分享这个抽奖工具建议明确标注“仅供学习交流使用”并附上字体文件和音效素材的开源许可信息。我用的思源黑体是SIL Open Font License可以免费商用但字体文件本身不能单独拿出来售卖。背景音乐如果是从网上找的素材务必确认是CC0协议或无版权限制避免二次分发时产生纠纷。技术层面还有一个容易被忽略的点单HTML文件虽然可以本地运行但如果通过微信或钉钉发送给别人对方直接点击预览时可能会因为浏览器的安全限制而无法读取本地文件。建议在分享时同时附上使用说明文档并明确告知对方“文件下载到电脑后用浏览器打开”而不是在聊天软件里直接预览。6. 写在最后的小经验做了这么多次现场抽奖项目我个人最大的感受是这种工具的技术难度真的不算高真正的挑战在于对现场不确定性的预判和兜底。技术方案要足够简单、足够健壮才腾得出精力处理现场的突发情况。最后分享一个压箱底的小技巧活动现场务必准备一台手机热点作为网络备份。虽然这套工具不依赖网络运行但备用电脑同步文件、临时给名单拍照确认、应急下载字体之类的事情还是需要网络的。一旦主办方提供的WiFi出问题手机热点能救全场。另外每次彩排结束后记得再点一次“开始抽奖”并让它完整跑完一轮确认所有数据重置正常再关闭页面——别问我是怎么知道的。本文还有配套的精品资源点击获取