ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

年会抽奖HTML源码:不重复抽取与现场避坑指南

年会抽奖HTML源码:不重复抽取与现场避坑指南 简介这是一套面向公司年会、团建活动场景的前端抽奖源码基于HTML、CSS与JavaScript实现适合具备基础前端能力、希望快速搭建现场互动环节的开发者使用。资源支持动态配置员工信息、奖品灵活调整并实现员工仅能中奖一次的逻辑同时内置多种震撼音效与酷炫视觉样式可直接运行index.html使用。压缩包共20个文件约34.12MB包含html主页面、css样式、js脚本以及mp4背景视频、mp3抽奖音效、png与jpg图片素材、psd设计源文件和readme说明覆盖页面结构、交互逻辑与视听资源。目前已有6867人学习下载读者可参考其抽奖逻辑、音效切换与样式调整方式快速替换员工与奖品数据用于年会现场或活动互动环节。1. 年会抽奖页面的真实需求不是炫技是别在老板面前翻车年会抽奖这个需求几乎每年年底都会有人来问一遍。表面上看是「html 实现酷炫的公司年会抽奖」实际落到执行层面真正被关心的从来不是动画有多花哨而是三件事名单能不能一次导入几百上千人、抽出来的结果能不能当场服众、投影到大屏上字够不够大够不够亮。我见过太多现场翻车的案例——滚动动画卡成 PPT、抽到的人重复出现、老板临时说「把三等奖改成抽 8 个」结果代码里写死了 5 个最后只能尴尬地重抽。所以这篇不聊虚的就围绕一份能直接跑的 html 抽奖源码把结构、随机算法、动画性能、名单导入、现场应急这几件事讲透。它适合两类人一类是行政或 HR 被临时抓来做个抽奖页需要照着改就能用另一类是前端想拿它当个练手项目顺便搞清楚大屏场景下 DOM 动画和 canvas 动画到底怎么选。核心词 html、抽奖、源码会贯穿始终因为这三样东西决定了你能不能在一个下午之内把它做出来并跑通。我一般会把整个方案拆成「静态页面骨架 抽奖逻辑 名单数据 现场兜底」四块其中真正容易出问题的只有后两块。下面从最小可运行版本开始一步步加东西每一步都能单独验证。2. 从零搭一个能跑的抽奖页DOM 结构、样式与随机逻辑2.1 最小可运行骨架一个按钮、一块滚动区、一份名单先别急着上 canvas 和粒子特效第一版只要能点一下开始、名字滚起来、再点一下停下来出结果就算成功。这个版本大概 80 行代码是后面所有花活的地基。!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title年会抽奖/title style /* 大屏场景字号用 vw保证投影后依然清晰 */ body { margin: 0; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #0b1020; color: #fff; font-family: Microsoft YaHei, sans-serif; overflow: hidden; } #nameBox { font-size: 8vw; font-weight: 700; letter-spacing: 0.1em; min-height: 1.2em; text-shadow: 0 0 30px rgba(80, 160, 255, 0.8); } #startBtn { margin-top: 6vh; padding: 1.2vh 5vw; font-size: 2vw; border: none; border-radius: 999px; background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff; cursor: pointer; } #startBtn:disabled { opacity: 0.5; cursor: not-allowed; } /style /head body div idnameBox准备开始/div button idstartBtn开始抽奖/button script // 名单先写死后面会换成外部导入 const pool [张三, 李四, 王五, 赵六, 钱七, 孙八]; const nameBox document.getElementById(nameBox); const startBtn document.getElementById(startBtn); let timer null; let running false; startBtn.addEventListener(click, () { if (!running) { // 开始滚动每 50ms 换一个名字 running true; startBtn.textContent 停止; timer setInterval(() { const idx Math.floor(Math.random() * pool.length); nameBox.textContent pool[idx]; }, 50); } else { // 停止清掉定时器当前显示的名字就是结果 running false; clearInterval(timer); startBtn.textContent 开始抽奖; } }); /script /body /html这段代码里几个参数值得说清楚。setInterval的 50ms 是滚动速度数值越小滚得越快低于 30ms 人眼基本看不清高于 100ms 会显得卡顿我一般用 40 到 60 之间。font-size: 8vw是按视口宽度算的1920 宽的投影上大概是 153px后排也能看清。min-height: 1.2em是为了防止名字长短不一导致按钮跳动这个细节不做的话滚动时整个页面会上下抖现场看着很廉价。提示这个版本用的是Math.random()它本身没问题但如果你要「不重复抽取」就不能只靠它得配合一个已抽集合下一节会讲。2.2 不重复抽取用已抽集合而不是删数组很多人第一反应是抽一个就从数组里splice掉一个这样确实不会重复但有个隐患一旦现场需要「重抽」或者「把某个人放回去」数组已经被改得面目全非恢复起来很麻烦。更稳的做法是保留原始名单不动用一个Set记录已经抽过的人。const pool [张三, 李四, 王五, 赵六, 钱七, 孙八]; const drawn new Set(); // 已抽中的人 function pickOne() { // 过滤掉已抽中的剩下的才是候选 const candidates pool.filter(name !drawn.has(name)); if (candidates.length 0) { return null; // 抽完了 } const idx Math.floor(Math.random() * candidates.length); const winner candidates[idx]; drawn.add(winner); return winner; }逻辑说明每次抽取前先算出「还没中奖的人」从这个子集里随机取一个取完加进drawn。这样原始pool始终完整想重置只需要drawn.clear()。参数上唯一要注意的是candidates.length 0的判断抽完最后一轮必须给个提示否则页面会一直显示上一个人现场会以为卡住了。2.3 把名单换成外部文件一次导入几百人写死在代码里的名单只适合演示真实年会少则几十人多则上千人必须支持外部导入。最省事的方案是读一个同目录下的names.js里面导出一个数组这样不用起服务器双击 html 就能跑。// names.js window.NAMES [ 张三, 李四, 王五, 赵六, 钱七, 孙八, // ... 继续加 ];!-- 在抽奖脚本之前引入 -- script srcnames.js/script script const pool window.NAMES || []; if (pool.length 0) { document.getElementById(nameBox).textContent 名单为空请检查 names.js; } /script为什么不用fetch读 txt 或 csv因为fetch在file://协议下会被浏览器拦截除非你起一个本地服务器。年会现场最怕的就是「还要装个环境」所以names.js这种直接挂全局变量的方式最稳。名单格式就是一行一个名字用引号包起来逗号分隔从 Excel 复制出来稍微处理一下就能用。注意如果名单里有英文名或特殊符号记得检查引号是否配对一个漏掉的引号会让整个names.js报错页面直接白屏。3. 让抽奖看起来「酷炫」动画方案选型与性能边界3.1 DOM 动画和 canvas 动画年会场景该选哪个「酷炫」这个词很虚落到技术上其实就是滚动、缩放、发光、粒子这几类效果。DOM 方案用 CSS 的transform和opacity做动画浏览器会走合成层性能不差canvas 方案把所有东西画在一张画布上适合粒子多、元素多的场景。年会抽奖的元素数量其实很少——一个名字、几个按钮、最多加个背景所以 DOM 方案完全够用而且改起来直观不用去算坐标。我一般只在一种情况下上 canvas背景要做几百个粒子飘动或者名字要用自定义字体逐字渲染。除此之外DOM CSS 动画是性价比最高的选择。下面这个滚动效果就是在 2.1 的基础上加了缩放和模糊视觉上会「高级」不少。#nameBox { transition: transform 0.08s ease-out, filter 0.08s ease-out; } #nameBox.rolling { transform: scale(1.08); filter: blur(1px); }// 滚动时加 class停止时去掉 timer setInterval(() { const idx Math.floor(Math.random() * pool.length); nameBox.textContent pool[idx]; nameBox.classList.add(rolling); }, 50); // 停止时 clearInterval(timer); nameBox.classList.remove(rolling);transition的 0.08s 要略大于setInterval的间隔否则动画还没结束就被下一次替换看起来会抖。blur(1px)是模拟「高速运动看不清」的感觉数值别超过 2px否则名字糊成一片现场会有人喊看不清。3.2 中奖弹窗与结果记录别让抽过的人再上来抽奖抽到后面最尴尬的是有人重复中奖。除了 2.2 的drawn集合界面上也要有反馈——中奖后弹个窗同时把结果记到一个列表里方便最后统一展示。function showWinner(name) { const modal document.createElement(div); modal.className winner-modal; modal.innerHTML div classwinner-name${name}/div; document.body.appendChild(modal); // 3 秒后自动关闭 setTimeout(() modal.remove(), 3000); }.winner-modal { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.7); z-index: 999; animation: pop 0.3s ease-out; } .winner-name { font-size: 10vw; font-weight: 900; color: #ffd700; text-shadow: 0 0 40px rgba(255, 215, 0, 0.9); } keyframes pop { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }inset: 0是top/right/bottom/left的简写兼容性在现代浏览器没问题。z-index: 999保证弹窗盖住所有内容。3 秒自动关闭是给现场留出欢呼的时间太短没气氛太长会挡住下一次抽奖。3.3 大屏适配1920 和 4K 投影下的字号与布局年会现场的设备五花八门有 1080P 投影也有 4K 大屏甚至有人直接用笔记本外接。用vw和vh做单位能自动适配但要注意 4K 屏上8vw会变得巨大可能超出屏幕。稳妥的做法是给字号加一个上限。#nameBox { font-size: clamp(48px, 8vw, 160px); }clamp(最小值, 理想值, 最大值)是 CSS 里做响应式字号最省心的函数48px 保证小屏可读160px 防止 4K 屏上溢出。按钮、弹窗的字号同理都可以用clamp包一层。另外背景色建议用深色投影仪在明亮环境下深色背景对比度更高名字用白色或金色别用红色——红色在有些投影上会偏暗后排看不清。提示现场调试时按 F11 全屏再按 Ctrl 加 或 - 调整缩放比改代码快得多。4. 现场避坑抽奖页面最容易翻车的 5 个地方4.1 现象滚动动画卡顿名字一跳一跳原因setInterval的间隔太短或者每次滚动都在改textContent触发了重排。DOM 操作本身不慢但高频改文字会让浏览器反复计算布局。解决把间隔控制在 40ms 以上并且滚动时只改文字不要同时改样式。如果还卡把nameBox加一个will-change: transform提前告诉浏览器这层要动。4.2 现象抽到的人重复出现原因只用了Math.random()没有记录已抽中的人。随机函数本身不保证不重复抽 10 次可能同一个人出现 3 次。解决用 2.2 的drawn集合每次从「未中奖」的子集里抽。如果现场要求「可以重复中奖」那就把drawn去掉但要在规则里说清楚。4.3 现象名单导入后页面白屏原因names.js里有语法错误比如中文引号、漏逗号、最后一行多了逗号。浏览器解析失败会直接中断脚本。解决打开浏览器控制台看报错行号大概率是引号问题。建议用编辑器把names.js格式化一遍或者用 JSON 校验工具过一下。4.4 现象点「停止」后结果和滚动时看到的不一样原因clearInterval和读取textContent之间有延迟或者停止逻辑里又调了一次随机。解决停止时先clearInterval然后直接读当前nameBox.textContent作为结果不要再调pickOne。顺序反了就会出现「看到的和抽到的不一致」现场会有人质疑。4.5 现象投影上字太小后排看不见原因用了固定px字号或者vw在低分辨率设备上算出来偏小。解决用clamp设下限并且现场提前试投。如果实在看不清把名字区域改成整屏显示按钮缩小放角落保证名字最大化。5. 进阶技巧把抽奖结果导出成表格以及一个我常用的现场习惯抽奖结束后行政通常要一份中奖名单发通知所以导出功能比想象中重要。最简单的做法是用Blob生成一个 csv 文件点一下就能下载。function exportWinners(winners) { // winners 是数组每项 { name, prize } const header 姓名,奖项\n; const rows winners.map(w ${w.name},${w.prize}).join(\n); const blob new Blob([header rows], { type: text/csv;charsetutf-8 }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download 中奖名单.csv; a.click(); URL.revokeObjectURL(url); }type里的charsetutf-8必须加否则 Excel 打开中文会乱码。URL.revokeObjectURL是释放内存虽然不写也不影响功能但养成习惯没坏处。导出的 csv 可以直接用 Excel 打开也可以再转成 xlsx。另一个我自己的习惯是现场一定准备两个版本一个正常版一个「老板临时改规则」版。改规则版里把奖项数量、每轮抽几个人都做成输入框而不是写死在代码里。年会现场变数太多能少改一次代码就少一次翻车机会。label本轮抽取人数input idcountInput typenumber value1 min1/labelconst count parseInt(document.getElementById(countInput).value, 10) || 1; const winners []; for (let i 0; i count; i) { const w pickOne(); if (w) winners.push(w); }这个输入框看着不起眼但真到现场老板说「三等奖一次抽 10 个」的时候你只需要改个数字不用碰代码。我吃过这个亏所以现在做任何抽奖页都会留这个口子。最后说个验证方法抽奖前用一份 100 人的测试名单跑 20 轮看有没有重复、有没有漏抽、导出是否正常。跑完把drawn清空再上正式名单。这个流程花不了五分钟但能挡掉九成的现场事故。希望帮到你。本文还有配套的精品资源点击获取
返回列表