纯前端年会抽奖源码拆解:HTML+CSS+JS实现名字滚动与随机停表
简介这是一份使用HTML、CSS和JavaScript编写的年会抽奖页面源码面向企业行政、活动策划者以及需要快速搭建抽奖环节的前端开发者适合直接套用或参考学习。源码为每个奖项设计了独立的奖品展示区未开奖时显示抽奖按钮点击后员工姓名会滚动闪烁停止时自动定位中奖者并弹出提示已抽奖的用户信息也会同步汇总到中奖用户列表中方便现场整体把控。压缩包共包含18个文件有页面结构文件、样式表、脚本库、背景图片和各个奖品的配图并附带演示视频与使用说明文档整体约96.45MB。目前已有3408人下载学习可以重点参考抽奖动画的实现方式包括定时控制、随机停位及状态记录等也可直接替换名单素材后投入实际年会使用。配合抽奖界面、特等奖界面、中奖用户界面等效果截图以及视频演示能够快速理解项目模块划分、启动方法以及如何修改样式与逻辑便于二次开发。1. 名字滚动年会抽奖为什么纯前端方案才是年会现场最稳的选择年底一到行政和人事就开始在群里喊谁写过 html 年会抽奖需求特别统一——大屏上名字滚动喊停就出中奖人要能抽好几轮关键是别连后台、别装软件U 盘拷过去双击就能跑。这份名字滚动年会抽奖源码干的就是这个事纯 HTML CSS JS 的网页制作无依赖名单放 txt 或数组里点开始滚动、点停止随机停表一套页面把滚动、去重、中奖展示全包了。适合三类人年会筹备想赶紧交差的非前端、想练 HTMLCSSJS 基础语法的新手、以及想快速改成自己公司方案的熟手。下面按我从拿到源码到现场跑通的顺序拆给你看重点写参数怎么改、哪些地方最容易现场翻车。2. 滚动与停表从 HTML 结构到随机算法的三层拆解抽奖页看起来简单拆开就是三件事怎么摆、怎么动、怎么停。这三件事正好对应 HTML、CSS、JS 三层各管各的互不掺和这也是这类源码最好改的地方。你想加个背景图不用动逻辑想换滚动节奏不用动布局想改成“抽部门再抽人”也不用拆页面。2.1 页面骨架三块式布局与数据源解耦拿到源码先看结构典型布局是三块式背景层管视觉、滚动区管展示、控制区管交互。背景层一般是全屏渐变色或年会主视觉图滚动区是中间一个固定高度的“视窗”名字在里面滚控制区放开始、停止按钮和中奖展示位。这个结构把“展示”和“控制”分开现场操作时手不会挡住大屏内容。div idstage div idbg/div div idrollArea div idrollBox!-- 名字滚动在这里 --/div /div div idctrl button idstartBtn开始/button button idstopBtn停止/button div idwinner/div /div /divstage 是外层容器固定宽高方便后面做整体缩放适配rollBox 是真正发生位移的元素所有名字都渲染在里面winner 展示中奖结果。名单不写死在 HTML 里而是由 script.js 里的数组渲染换名单时不动页面结构这是这类代码能快速复用的关键。2.2 滚动动效setInterval 与 CSS transform 的选择滚动实现上常见做法有两类一类是每隔几十毫秒改 innerHTML往列表里插一个新名字视觉上是“跳字”另一类是改 transform: translateY让整个列表连续上移视觉上是“连滚”。年会大屏我强烈建议用后者因为观众感觉名字一直在流动停的那一下才有悬念感跳字方案看着像刷新现场氛围差不少。// 滚动核心每 speed 毫秒把滚动区往上移一行 const rowH 80; // 每个名字的占位高度和 CSS 里 line-height 保持一致 let posY 0; // 当前偏移量 let timer null; function startRoll() { stopRoll(); // 防止重复点击开多个定时器 timer setInterval(() { posY - rowH; rollBox.style.transform translateY(${posY}px); rollBox.style.transition transform 0.1s linear; if (-posY rowH * names.length) { posY 0; // 滚完一轮回到顶部形成无缝循环 } }, 100); // 100ms 一帧大概每秒滚 12 行 }rowH 必须和 CSS 里名字行高完全一致差 1px 都会越滚越偏。100ms 是默认手感想更急促改成 60ms想稳重改成 150ms。无缝循环靠判断累计偏移超过总高度后归零这里要注意归零时要同步清掉 transition否则会看到一次明显的“瞬移回滚”。2.3 随机停表如何让“幸运儿”看起来既随机又公平停止逻辑是整个源码的技术核心。直接点停止立刻停会显得很生硬观众还没进入状态名字就钉住了。常见做法是做一个“减速停止”点击停止后不马上清定时器而是逐步拉长滚动间隔让列表越来越慢最后停在随机选中的名字上。目标索引用 Math.random() 生成但要过滤掉已中奖的人。function stopRoll() { clearInterval(timer); // 随机选中一个未中奖的索引 const unPicked names .map((n, i) ({ n, i })) .filter(item !pickedSet.has(item.i)); const target unPicked[Math.floor(Math.random() * unPicked.length)]; // 先快后慢的减速过程 let steps 10; const slowDown setInterval(() { posY - rowH; rollBox.style.transition transform ${steps * 10}ms ease-out; rollBox.style.transform translateY(${posY}px); steps--; if (steps 0) { clearInterval(slowDown); showWinner(names[target.i]); pickedSet.add(target.i); // 标记已中奖下一轮不再抽到 } }, 120); }这里最值得抄的是“减速感”的实现每走一步把 CSS transition 的时长从 100ms 逐步拉长到 1000ms滚动区看起来就像惯性快耗尽一样越来越沉。pickedSet 是去重核心第二轮开始自动跳过已中奖索引。注意 target 是在减速开始前就定好的整个减速过程只是表演结果早就锁定了这也避免了“停不住”的尴尬。3. 把源码跑起来文件清单、编码与本地服务器三条路源码拿到手别急着双击先花两分钟搞清楚包里有什么、编码对不对。这个项目九成的问题都出在文件编码和运行方式上提前看一眼能省下年会当天的一头汗。3.1 拿到 zip 后先看这四类文件解压后基本逃不出下面这套结构具体文件名以你拿到的 zip 为准但职责是固定的文件职责是否必改index.html页面结构引入 CSS 和 JS一般不改style.css布局、字体、主题色、滚动区尺寸改样式时动script.js名单数组、滚动与随机逻辑必改名单在这里names.txt外部名单每行一个名字取决于 script.js 是否读它README运行方式和参数说明看完就扔我的习惯是拿到手先打开 README 和 script.js 的名单区确认名单是写死在数组里还是走 fetch 读外部文件。写死数组适合小规模快速改fetch 适合现场加人时只动 txt 不碰代码。这两个模式混着用是后患后面避坑章节细说。3.2 直接双击运行file 协议下的限制与对策双击 index.html 是最快的验证方式但有两个坑要提前知道中文乱码和 localStorage 失效。乱码是因为文件保存编码和页面声明的不一致localStorage 失效是因为浏览器对 file:// 协议有限制中奖名单没法存档。# 先检查文件编码Linux/Mac 下直接看 file index.html # 期望输出 UTF-8 Unicode text如果输出带 ISO-8859 或 GBK 字样说明文件是 ANSI/GBK 编码保存的和 UTF-8 声明打架了。解决方式用 VS Code 打开文件点击右下角编码处选“通过编码保存”- UTF-8无 BOM保存后刷新。Windows 上没装 VS Code 的话用记事本另存为编码选 UTF-8 也能救。这个编码问题占这类求助帖的一半以上基本都是从 Windows 老机器上拷贝文件导致的。3.3 用本地服务器跑python 一行命令如果源码里用了 fetch 读 names.txt或者你指望中奖名单能存下来双击跑的方案就不够用了。在解压目录里起一个本地 HTTP 服务所有浏览器限制全部消失。# 在 zip 解压后的目录里执行 python -m http.server 8080然后浏览器访问 http://localhost:8080 就能看到抽奖页。8080 是端口被占用就换 8000、9090 都行。跑起来后改完名单刷新页面即时生效不用重启服务。附带一个好处同一局域网内其他电脑可以用 http://你的IP:8080 访问同一页面适合在后台放一台备用屏主屏挂了立刻顶上。4. 参数定制与名单接入三个改动点覆盖 90% 的年会需求年会抽奖的需求翻来覆去就那几样换名单、调节奏、换皮肤。这三个改动点你掌握了基本不用求人改代码。4.1 名单从哪来txt、CSV 与 JS 数组三种喂法名单接入是必改项按规模选方式。一百人以内直接改数组最直白字段就一个名字注释里写明“按行添加”。// script.js 里最显眼的位置 const names [ 张伟, 李娜, 王强, 赵敏, 刘洋, 陈静, 杨帆, 周凯 ];超过一百人或者现场会临时加人推荐走外部 txt。注意 fetch 必须在本地服务器下跑file 协议会被浏览器拦截。// 用 fetch 读同目录 names.txt每行一个名字 fetch(names.txt) .then(res res.text()) .then(text { window.names text.split(\n).map(s s.trim()).filter(Boolean); });split 之后做 trim 和 filter 是必须的否则文件末尾多一个空行名字数组里就多个空字符串滚动时会出现一截空白。txt 文件必须 UTF-8 编码和 HTML 声明保持一致。喂法适用规模注意点JS 数组写死≤100 人改完要强刷缓存txt 每行一个任何规模必须 UTF-8 编码CSV 两列部门,姓名大规模展示部门解析逗号时注意姓名别带逗号CSV 喂法适合要显示“部门-姓名”的场景解析时按行再按逗号切分部门字段可以拼在名字前面显示但随机抽取逻辑依然只认第二个字段别把部门也塞进抽奖池。4.2 滚动速度与停表时长参数怎么调才不翻车调参的核心目标就一个让滚动看起来有抽奖的感觉而不是像列表在刷新。下面几个参数直接决定手感参数默认值作用调参建议interval100ms每帧间隔60ms 急促150ms 稳重rowH80px每行高度与 CSS 行高严格一致steps10减速步数少于 5 步会急刹车transition0.1s单帧动画时长减速时逐步拉长调参逻辑是先定名单长度再反推行高和速度。三百人的大名单rowH 要压到 60px 以下否则滚完一整轮要十几秒停止时观众等得不耐烦。现场经验是滚 3~5 秒能出结果是最佳节奏这个手感在 100 人名单、100ms interval、60px rowH 的组合下测出来比较理想。4.3 样式换肤CSS 变量改背景、字体与高亮色主题换色不用动 JS源码里只要用了 CSS 变量改 :root 段落的几个值就够了。:root { --bg-color: #0a0e27; /* 年会暗场背景深蓝黑 */ --font-color: #ffd700; /* 名字金色大屏上醒目 */ --highlight-color: #ff3b3b; /* 停止时高亮红色 */ --roll-area-height: 240px; /* 滚动视窗高度 */ --row-height: 80px; /* 每行名字高度和 JS 里 rowH 同步 */ }这里有个这类源码最容易忽视的“双端同步”陷阱CSS 里的 --row-height 和 JS 里的 rowH 必须一致。你改了 CSS 里行高忘了改 JS滚动就会越滚越偏名字逐渐偏离高亮框现场非常尴尬。我的习惯是改完先数五秒滚动看名字是否始终保持在视窗中间。5. 避坑与排查年会现场最怕的五个突发状况这一章是血泪经验集合。前面把源码跑通不难真正要命的是现场那一堆“玄学”问题——明明在自己电脑上好的换台机器全乱套。下面按现象、原因、解决的顺序写你直接对号入座。5.1 三个高频现场事故乱码、没反应、看不清现象1双击打开 index.html名字全是“锟斤拷”或者方块字。 原因文件是 GBK/ANSI 编码保存的页面声明的却是两边对不上浏览器按 UTF-8 解码 GBK 文件就乱码了。这种情况最常见于 Windows 老机器上用记事本默认另存的文件。 解决用 VS Code 打开文件右下角编码选“通过编码保存”- UTF-8无 BOM。名单如果是单独 txt同样处理。改完刷新再看乱码消失。现象2点“开始”按钮没反应F12 控制台报 SecurityError 或 Failed to fetch。 原因双击打开时地址栏是 file:// 开头浏览器对 localStorage 和 fetch 都做了限制。源码里如果用了 fetch 读外部名单或者中奖名单要写 localStorage在 file 协议下全会挂。 解决不要在 file 协议下跑在解压目录执行 python -m http.server 8080访问 localhost:8080。如果现场机器没装 Python可以用 npx serve 或者干脆手写一个临时 HTML 内嵌名单避开 fetch。现象3滚动速度太快观众说“啥都没看清就停了”。 原因默认 interval 偏小且名单短几十人的名单用 60ms 的帧间隔滚动像翻书。 解决把 interval 从 100ms 调大到 150ms或者把 rowH 调大让单行更占屏。如果现场来不及改代码就晚两秒按停止按钮让滚动先跑一会儿观感会好很多。5.2 两个边界逻辑重复中奖与现场加人现象4第二轮抽奖又抽到第一轮的人。 原因中奖者没有从待抽名单里移除。常见于改动过的源码pickedSet 逻辑被删了或者是每轮重置了 names 数组。 解决确认停止逻辑里调用了 pickedSet.add(当前索引)并且在取随机索引前用 filter 过滤掉已中奖索引。标准写法就是 2.3 里那段先 map 带索引再 filter最后随机取。少了 filter 那行去重就是假的。现象5现场临时加了几个人刷新页面名单还是旧的。 原因名单写死在 JS 数组里浏览器缓存了旧版本 script.js或者现场用的是 file 协议导致 fetch 读不到新 txt。 解决优先把名单外置为 names.txt加人只改 txt刷新即生效。如果名单在 JS 数组里改完用 CtrlShiftR 强刷并叮嘱现场工作人员开无痕窗口。另外涉及 localStorage 的存档功能必须在 http 协议下测试file 协议下你会误以为代码写错了。6. 进阶玩法大屏适配、音效与中奖名单本地持久化年会现场环境比办公室复杂得多投影仪、LED 屏、电视拼接墙分辨率和缩放比例都不一样。下面三个进阶点是把这套源码从“能跑”推到“现场扛得住”的关键。6.1 大屏适配等比缩放的写法与其写一堆媒体查询不如固定 1920×1080 的舞台尺寸再用 transform 等比缩放到实际屏幕。这样页面布局始终不变只是整体放大缩小不会出现错位。function fitScreen() { const scale Math.min(window.innerWidth / 1920, window.innerHeight / 1080); document.getElementById(stage).style.transform scale(${scale}); } window.addEventListener(resize, fitScreen); fitScreen();这里注意 stage 的 transform-origin 要设成 0 0否则缩放会以中心为基准页面跑偏。启动时先调一次 fitScreen窗口尺寸变化时再调双保险。LED 屏的分辨率如果特别怪比如 1536×2048 竖屏这个方案也能应对居中后再加一层 flex 布局就行。6.2 用 Web Audio 做抽奖音效不引外部文件年会现场最怕音频文件丢失U 盘拷来拷去少个 mp3音效就没了。用 Web Audio API 现场合成音效代码里带声音不依赖任何外部文件。const ctx new AudioContext(); function tick() { const osc ctx.createOscillator(); const g ctx.createGain(); osc.frequency.value 800; g.gain.value 0.3; osc.connect(g).connect(ctx.destination); osc.start(); osc.stop(0.08); }每滚动一帧调一次 tick()就是哒哒哒的滚动声停止时把频率降到 400、时长拉到 0.5 秒就是中奖的“叮”的一声。新手最容易踩的坑是浏览器禁止无交互的自动播放首次点击页面要调 ctx.resume()否则声音不响。我的习惯是把 resume 绑在开始按钮的点击事件里确保用户手势触发。6.3 中奖名单存档localStorage 防扯皮现场抽完奖最怕的就是“刚抽到谁来着”的讨论。把中奖名单写进 localStorage误关页面重开也能查还能导出给行政确认。let winners JSON.parse(localStorage.getItem(winners) || []); function saveWinner(name) { winners.push(name); localStorage.setItem(winners, JSON.stringify(winners)); }注意 localStorage 按源隔离只有 http 协议下才可靠file 协议下会报错。存档逻辑要挂在 showWinner 之后中奖名单显示的同时入库。后续如果想做“再来一轮”的复位只要清空 winners 数组并重新写入空数组即可。后来我把这整个流程固化成自己的交付模板了拿到类似的抽奖源码先通读一遍 script.js 的名单区和去重逻辑再起本地服务器跑通一次完整抽奖最后检查文件编码和缓存策略。从那以后我每次给行政交付抽奖页都强制自己先跑一遍“名单加载→抽 3 轮→刷新页面→再抽”的完整闭环确认名单不丢、不重复才敢说完工。希望帮到你。本文还有配套的精品资源点击获取