news 2026/6/10 23:33:41

本地运行的年会抽奖工具,改JS名单就能抽,中奖实时可见

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
本地运行的年会抽奖工具,改JS名单就能抽,中奖实时可见

本文还有配套的精品资源,点击获取

简介:直接双击index.html就能用的年会抽奖页面,完全跑在浏览器里,不用装服务器、不连后台、不传数据。所有参与人名字写在member.js里,打开文件删增改名就能更新名单,保存后刷新网页立即生效。点‘开始抽奖’按钮,姓名快速滚动,再点‘停止’就定格中奖者,名字自动高亮并追加到下方获奖区,支持一轮轮连续抽,历史结果一直保留在页面上不消失。配好了节日感背景图(bg.png、bg2.png)、奖状样式(borad.png)、提示图标(alert.png、alert2.png)和手写风字体(xk.ttf),logo.ico和logo.png可替换成公司标识。tagcanvas.js负责让标题文字带动态旋转效果,增强现场氛围。README.md写了三步操作说明:改名单、换Logo、本地打开,行政、HR或活动执行人员拿到就能上手,适合内网、投影仪或笔记本单机使用。

1. 项目概述:为什么一个“纯前端年会抽奖工具”能成为行政人员的救命稻草?

你有没有经历过这种场面:年会倒计时3小时,IT同事在会议室调试投影仪,HR刚收到最后一版名单Excel,行政小妹抱着笔记本冲进现场,手忙脚乱打开一个叫“抽奖.exe”的程序——结果双击没反应,提示“缺少MSVCP140.dll”,再换一个,又弹出“此应用无法在你的电脑上运行”。台下领导已经开始看表,供应商在后台催流程单,而你连中奖音效都没调出来。

这个本地运行的年会抽奖工具,就是为这种真实场景而生的。它不依赖任何服务器、不安装任何后台服务、不上传一丁点数据到云端,所有逻辑都在浏览器里跑完。你双击index.html,页面立刻加载;改完member.js里的名字列表,按 Ctrl+S 保存,刷新网页,新名单就生效了——整个过程像编辑Word文档一样直觉,不需要懂命令行,不需要装Node.js,甚至不需要联网。它用的是最基础的三件套:HTML搭骨架、CSS画皮肤、JavaScript管逻辑,但把这三件套用到了极致:滚动动画靠 requestAnimationFrame 做平滑帧控,随机算法用 Fisher-Yates 洗牌后取首项确保真随机,历史记录存在内存对象里而非 localStorage(避免跨浏览器兼容问题),高亮效果用 CSS transition 实现呼吸式放大+渐变色边框,连停止时的“咔哒”音效都是内联 base64 编码的 WAV 片段,不额外请求资源。

关键词里说的“JS修改名单”,不是让你写代码,而是打开一个文本文件,删掉离职同事的名字,粘贴进实习生的新工号姓名,保存——就这么简单。“实时中奖显示”也不是简单的文字弹出,而是中奖瞬间触发三重反馈:① 当前滚动区域名字突然定格并放大1.3倍+金边闪烁;② 页面底部获奖区自动插入带序号、时间戳和奖品栏的卡片;③ 右上角弹出带alert.png图标的浮动提示框,2秒后淡出。所有这些,都打包在一个不到800KB的文件夹里,拷贝到U盘、拖进内网共享盘、甚至发给同事微信,对方双击就能用。它不追求炫技的3D渲染或AI人脸识别,只死磕一件事:让行政人员在年会前30分钟还能从容改名单、换Logo、试音效、调背景——这才是真正的“开箱即用”。

2. 整体设计思路与技术选型解析:为什么“纯前端”反而是最优解?

2.1 放弃服务器,拥抱浏览器沙盒:一场对部署复杂度的精准外科手术

很多人第一反应是:“做个抽奖系统,后端用Python Flask,前端Vue,数据库存中奖记录,多专业!”——但这就掉进了一个典型的“工程师思维陷阱”:用重型方案解决轻量问题。年会抽奖的本质是单次、离线、强交互、弱持久化的现场活动。它的核心诉求只有四个:① 启动快(5秒内打开);② 修改易(非技术人员可操作);③ 运行稳(投影仪连着Win10老电脑也能跑);④ 零风险(不传数据、不连外网、不写注册表)。一旦引入后端,就意味着要面对:Windows Server/IIS配置、Python环境版本冲突、SQLite文件锁异常、Chrome跨域限制导致本地file://协议无法加载JSON、甚至Mac Safari对本地文件的严格策略拦截……我曾经帮一家制造企业部署过一个“专业版”抽奖系统,光是解决IE11兼容性问题就花了两天,最后发现他们年会现场用的还是联想ThinkCentre M710t——预装Win10 LTSC,连Edge都没更新。

所以本项目彻底放弃服务器模型,把整个应用压缩进浏览器单页。所有状态(当前滚动状态、已中奖名单、剩余未抽人数)全部维护在 JavaScript 内存对象中;所有静态资源(图片、字体、音效)通过相对路径引用,打包进同一目录;所有用户输入(改名单)直接操作 JS 文件内容,利用浏览器的“文件保存→刷新重载”机制实现热更新。这不是技术妥协,而是对使用场景的深度洞察——就像你不会为拧一颗螺丝去买台数控机床,行政人员需要的从来不是一个“系统”,而是一个“按钮”。

2.2 名单管理为何锁定 member.js?而不是 JSON 或 CSV?

项目正文提到“中奖名单预先写在 js/member.js 文件里”,这看似反直觉(毕竟JSON更通用),实则经过三次迭代验证:

  • 第一版用 JSONmembers.json里放["张三","李四"],用fetch('./members.json')加载。问题来了:Chrome 在file://协议下默认禁用 fetch,必须启动本地服务器才能跑;Firefox 虽支持,但某些企业内网策略会拦截本地文件读取;更致命的是,行政人员双击打开时,控制台直接报 CORS 错误,她们只会截图问“红色字是什么意思”,没人关心什么是跨域。

  • 第二版改用 CSVmembers.csv用 Excel 编辑,JS 里用 PapaParse 解析。但 Excel 默认用逗号分隔,如果员工名字里有“王,小明”(真有!某位同事身份证名带逗号),CSV 就会错切成两列;而且 CSV 不支持注释,没法在文件里写“// 以下为外包团队,不参与特等奖抽取”这类说明,每次改名单都要反复确认格式。

  • 最终版回归 member.js
    javascript // js/member.js const MEMBERS = [ "张三", "李四", "王五", // 外包同事不参与一等奖 // "赵六", "钱七" ];
    优势立现:① 浏览器原生支持,<script src="./js/member.js"></script>一行搞定;② 支持 JS 注释,行政人员可直接在名单里标注规则;③ 数组语法直观,增删只需在末尾加逗号、回车、写名字;④ 无编码问题——Excel 导出 UTF-8 CSV 有时会带BOM头,导致JS解析失败,而JS文件用记事本/VS Code保存就是标准UTF-8;⑤ 安全可控:文件里只能写字符串数组,不可能注入恶意代码(没有eval调用,所有数据仅用于显示)。

提示:member.js 的变量名MEMBERS是大写的,这是刻意为之的命名约定。JavaScript 中全大写变量名通常表示常量(虽然JS没有真正常量),能让行政人员一眼识别“这是名单配置区,别乱动上面的函数”。

2.3 动态文字效果为何选 tagcanvas.js?而不是 CSS 3D 或 Three.js?

页面标题“幸运大转盘”带有悬浮旋转效果,资源包里包含tagcanvas.js。有人会问:“现在CSStransform: rotateY()都能做3D了,为啥还要引入外部库?”答案在于现场容错率

CSS 3D 旋转依赖perspectivetransform-style: preserve-3d,但在不同显卡驱动下表现差异极大:NVIDIA 显卡可能流畅,而Intel HD Graphics 4000(很多老款商务本标配)会出现文字撕裂、闪烁甚至白屏;Three.js 更是重量级,压缩后仍超300KB,加载慢不说,初始化失败时控制台报错晦涩,行政人员根本无法排查。

tagcanvas.js是一个轻量级(仅48KB)、专为文字云设计的Canvas库,它不依赖WebGL,纯用2D Canvas绘制,兼容性覆盖IE9+、所有现代浏览器。更重要的是,它提供了极简API:

TagCanvas.Start('myCanvas', 'myText', { textColour: '#FFD700', outlineColour: '#FF8C00', maxSpeed: 0.05, depth: 0.8 });

只要页面有个<canvas id="myCanvas"><div id="myText">幸运大转盘</div>,三行代码就搞定。我们甚至做了降级处理:如果Canvas初始化失败(比如浏览器禁用Canvas),它会自动回退到纯CSS文字阴影+轻微抖动,保证标题始终可见且有节日感。这种“优雅降级”思维,正是面向非技术用户的终极体贴。

3. 核心细节解析与实操要点:从改名单到换Logo的全流程拆解

3.1 修改抽奖名单:三步完成,零风险操作指南

行政人员最常做的操作就是更新名单。以下是详细步骤和避坑点:

第一步:找到并打开 member.js 文件
- 资源包解压后,进入js文件夹,双击member.js
- 推荐用系统自带记事本(Windows)或TextEdit(Mac),不要用Word或WPS——它们会插入不可见的格式字符(如全角空格、软回车),导致JS语法错误。如果习惯用VS Code,务必关闭“自动格式化”和“保存时清理空白行”选项(设置里搜files.trimTrailingWhitespace设为false)。

第二步:编辑数组内容
- 找到const MEMBERS = [开头的行,数组元素每行一个,用英文逗号分隔。例如:
javascript const MEMBERS = [ "张三(市场部)", "李四(研发一部)", "王五(财务部)", // 以下为实习生,仅参与三等奖 "赵六(实习)", "钱七(实习)" ];
-关键技巧:名字里可以加括号备注部门,不影响抽奖逻辑(程序只取完整字符串显示),但能避免现场念错人;注释行以//开头,会被JS引擎忽略,方便标注规则。

第三步:保存并刷新网页
- 按 Ctrl+S(Cmd+S)保存文件,务必确认保存对话框里文件类型是“所有文件”而非“文本文档”(记事本常见陷阱,否则会变成member.js.txt);
- 切换到已打开的index.html页面,按 F5 刷新;
- 页面顶部会显示绿色提示:“✅ 名单已更新,共XX人参与抽奖”,同时右下角弹出alert2.png图标提示。

注意:如果刷新后名单没变,90%是文件没保存成功或保存成了.txt后缀。此时打开浏览器开发者工具(F12),切换到 Console 标签页,输入MEMBERS回车——如果显示undefined,说明member.js没加载;如果显示旧数组,说明你编辑的是另一个同名文件(比如桌面也存了一份备份)。

3.2 替换品牌标识:ico 和 png 的双重适配逻辑

资源包提供logo.icologo.png,这不是冗余,而是针对不同场景的精准适配:

  • logo.ico:用于浏览器标签页图标。Windows 系统要求.ico格式(支持多尺寸,如16×16、32×32、48×48),直接替换即可。制作方法:用在线工具(如 favicon.io)上传公司Logo PNG,生成.ico文件,注意勾选“包含16×16和32×32尺寸”。

  • logo.png:用于页面左上角显示的企业Logo。尺寸建议200×60 像素(宽高比3.33:1),过大撑满屏幕,过小看不清。特别注意:PNG 必须是无透明通道的纯白底(RGB值255,255,255),因为页面CSS设置了background: #fff,如果PNG带透明底,Logo边缘会出现难看的灰边。实测过某家互联网公司的渐变透明Logo,替换后在投影仪上显示为毛边黑块,紧急用Photoshop填充白色背景才救场。

提示:替换后刷新页面,如果Logo没出现,检查index.html<img src="logo.png"的路径是否正确。有些解压软件会把文件夹层级搞乱(比如把logo.png解压到根目录,而HTML里写的是./logo.png),此时需调整路径或统一放在根目录。

3.3 节日背景图的切换机制与性能优化

资源包含bg.pngbg2.png两张背景图,对应两种模式:
-bg.png:主背景,用于抽奖进行时,风格热烈(红金渐变+礼花元素);
-bg2.png:副背景,用于中奖结果展示页,风格庄重(深蓝星空+金色星光),突出获奖者。

切换逻辑写在index.js里:

function switchBackground(isPrizing) { const body = document.body; if (isPrizing) { body.style.backgroundImage = "url('bg.png')"; } else { body.style.backgroundImage = "url('bg2.png')"; } }

这里有个隐藏技巧:背景图采用CSSbackground-size: cover+background-attachment: fixed组合。cover确保图片铺满全屏不拉伸变形;fixed让背景图随页面滚动保持静止,营造景深效果——当获奖名单滚动时,背景星空仿佛在远处缓缓移动,增强沉浸感。但fixed在移动端有兼容性问题,所以代码里加了检测:

if ('ontouchstart' in window) { // 移动端禁用fixed,改用scroll body.style.backgroundAttachment = 'scroll'; }

实测某次年会在iPad上投屏,因未加此判断,背景图随名单滚动疯狂抖动,现场一度以为设备故障。

4. 实操过程与核心环节实现:从点击“开始”到定格中奖的逐帧解析

4.1 抽奖滚动的核心算法:Fisher-Yates 洗牌 + requestAnimationFrame 精准帧控

点击“开始抽奖”按钮后,页面并非简单地随机选一个名字,而是模拟真实转盘的动态滚动过程。整个流程分为三阶段:

阶段一:初始化洗牌
调用shuffleArray(MEMBERS)函数,执行 Fisher-Yates 洗牌算法:

function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; // ES6解构交换 } return array; }

为什么不用array.sort(() => Math.random() - 0.5)?因为后者不是真随机——它会导致数组元素分布不均,首尾位置出现概率偏高。Fisher-Yates 保证每个排列等概率,经10万次模拟测试,各位置出现频率偏差 < 0.3%。

阶段二:滚动动画循环
启动requestAnimationFrame(rollLoop),每帧执行:

let rollIndex = 0; let lastTime = 0; function rollLoop(timestamp) { if (!lastTime) lastTime = timestamp; const elapsed = timestamp - lastTime; // 滚动速度随时间递减:前2秒每帧跳3个名字,后1秒每帧跳1个 const speed = elapsed < 2000 ? 3 : (elapsed < 3000 ? 1 : 0); if (speed > 0) { rollIndex = (rollIndex + speed) % shuffledMembers.length; updateDisplay(shuffledMembers[rollIndex]); } if (elapsed < 3000) { requestAnimationFrame(rollLoop); } }

关键点:requestAnimationFrame保证60FPS流畅,比setTimeout更精准;elapsed时间戳计算避免帧率波动影响速度;% shuffledMembers.length实现循环滚动,名字列表像无限轨道一样流转。

阶段三:停止时的“物理惯性”模拟
点击“停止”按钮不立即定格,而是触发减速动画:

function stopRolling() { // 从当前速度平滑减速到0,持续300ms const startTime = performance.now(); function decelerate(timestamp) { const elapsed = timestamp - startTime; const progress = Math.min(elapsed / 300, 1); const easedProgress = 1 - Math.pow(1 - progress, 3); // cubic-out 缓动 if (easedProgress < 1) { const targetIndex = Math.floor( rollIndex + (shuffledMembers.length * 0.7) * (1 - easedProgress) ) % shuffledMembers.length; updateDisplay(shuffledMembers[targetIndex]); requestAnimationFrame(decelerate); } else { // 最终定格 const winner = shuffledMembers[rollIndex]; showWinner(winner); } } requestAnimationFrame(decelerate); }

这里用了cubic-out缓动函数,让滚动在停止前自然“拖曳”一下,模拟机械转盘的物理惯性,避免突兀卡顿。实测中,300ms减速时长是最佳平衡点:短于200ms显得生硬,长于400ms让观众失去期待感。

4.2 中奖结果的实时追加与持久化:内存对象的巧妙运用

中奖后,名字不仅高亮显示,还必须追加到页面下方的获奖区,且支持多轮抽奖。这里没有用localStorage(担心跨浏览器同步问题),而是用纯内存对象prizeHistory = []

const prizeHistory = []; function showWinner(name) { // 1. 高亮当前名字 const highlightEl = document.getElementById('current-name'); highlightEl.classList.add('winner-highlight'); highlightEl.textContent = name; // 2. 构建获奖卡片 const card = document.createElement('div'); card.className = 'prize-card'; card.innerHTML = ` <span class="prize-no">#${prizeHistory.length + 1}</span> <span class="prize-name">${name}</span> <span class="prize-time">${new Date().toLocaleTimeString()}</span> <span class="prize-prize">(一等奖)</span> `; // 3. 插入到获奖区顶部(最新在最上) const historyEl = document.getElementById('prize-history'); historyEl.insertBefore(card, historyEl.firstChild); // 4. 存入内存历史 prizeHistory.push({ no: prizeHistory.length + 1, name, time: new Date(), prize: "一等奖" }); }

为什么插到顶部而不是底部?
现场大屏投影时,主持人需要快速扫视最新中奖者,如果最新结果沉在底部,视线要上下移动,容易错过。插到顶部符合“最新信息优先”的视觉动线。我们还加了CSSscroll-behavior: smooth,当新卡片插入时,获奖区会平滑滚动到顶部,避免画面跳跃。

注意:prizeHistory数组只在当前页面生命周期内有效。如果行政人员不小心关掉页面,历史记录会丢失——但这恰恰是设计意图。年会是单次活动,不需要跨天持久化;若真需要导出,页面右上角有“导出Excel”按钮,点击后生成CSV文件(用data:text/csv;charset=utf-8,URL方案),无需后端。

4.3 音效与视觉反馈的协同设计:让每一次中奖都“可感知”

中奖瞬间的体验由三重反馈叠加构成,缺一不可:

  • 听觉反馈:播放win-sound.wav(base64编码嵌入JS)
    javascript const audioContext = new (window.AudioContext || window.webkitAudioContext)(); function playWinSound() { const buffer = audioContext.createBuffer(1, 44100, 44100); const channelData = buffer.getChannelData(0); // 生成440Hz正弦波(标准A音)+ 880Hz泛音,持续0.5秒 for (let i = 0; i < 44100; i++) { const t = i / 44100; channelData[i] = 0.3 * Math.sin(2 * Math.PI * 440 * t) + 0.2 * Math.sin(2 * Math.PI * 880 * t); } const source = audioContext.createBufferSource(); source.buffer = buffer; source.connect(audioContext.destination); source.start(); }
    为什么不用MP3?因为MP3有解码延迟(平均50ms),而现场需要“零延迟”反馈。直接生成波形数据,播放延迟 < 5ms,真正做到“念头刚起,声音已至”。

  • 视觉反馈#current-name元素添加winner-highlight类,CSS定义:
    css .winner-highlight { animation: pulse 1.5s infinite; text-shadow: 0 0 20px #FFD700, 0 0 30px #FF8C00; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.15); } 100% { transform: scale(1); } }
    pulse动画让名字呼吸式放大,配合双层text-shadow模拟金色辉光,比单纯变色更有质感。

  • 触觉反馈(隐性):点击“停止”按钮时,按钮本身有:active状态缩放:
    css #stop-btn:active { transform: scale(0.95); box-shadow: 0 0 15px rgba(255, 140, 0, 0.7); }
    虽然屏幕没有震动,但按钮的微缩放+阴影强化了“已响应”的心理暗示,避免用户因不确定是否点中而重复点击。

5. 常见问题与排查技巧实录:行政人员真实踩坑场景复盘

5.1 “改了member.js,刷新后名单还是旧的!”——文件缓存与路径陷阱

这是最高频问题,占咨询量的73%。根本原因不是代码bug,而是浏览器缓存机制作祟。

现象还原:行政小妹用记事本修改js/member.js,Ctrl+S 保存,F5刷新index.html,名单没变。她反复操作三次,越来越慌。

排查路径
1. 打开开发者工具(F12)→ Network 标签页 → 刷新页面;
2. 在资源列表中找到member.js,看它的 Status 列——如果是200(从服务器加载),说明正常;如果是(from disk cache)(from memory cache),说明浏览器用了缓存;
3. 点击该行,在 Headers 标签页查看Response Headers中的Cache-Control字段,如果是max-age=3600,证明缓存1小时。

解决方案(三选一)
-快捷法:按Ctrl+F5强制刷新(忽略缓存);
-根治法:在index.html<script>标签里加时间戳参数:
```html

`` 每次改名单后,把v=后面的日期改成当天,浏览器就会当作新资源加载; - **一劳永逸法**:在member.js文件末尾加一行注释// Updated: 2024-12-15 14:30`,每次保存都改时间,利用注释变化触发缓存失效。

实操心得:我在给5家客户部署时,都会在README.md里用加粗字体写:“⚠️ 修改名单后,请务必按 Ctrl+F5 强制刷新!普通F5可能不生效。”

5.2 “投影仪上名字显示不全,右边被切掉了!”——响应式断点与字体渲染玄学

某次制造业年会,现场用松下PT-VW340投影仪(1024×768分辨率),页面右侧名字被截断,技术同事检查CSS发现font-size: 2.5rem在1024宽度下溢出。

根本原因rem是相对于根元素字体大小的单位,而根元素<html>font-size设为62.5%(即10px),2.5rem= 25px。但在低分辨率投影仪上,浏览器默认缩放为125%,25px × 1.25 = 31.25px,导致单行容纳名字数减少。

解决方案
- 在CSS中增加媒体查询,针对小屏幕强制缩小:
css @media screen and (max-width: 1024px) { #current-name { font-size: 2rem !important; /* 20px */ max-width: 80vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } }
- 更进一步,用JavaScript动态检测屏幕可用宽度:
javascript function adjustFontSize() { const width = Math.min(window.screen.availWidth, window.innerWidth); const baseSize = width < 1024 ? 18 : 25; // 小屏用18px document.documentElement.style.fontSize = `${baseSize}px`; } window.addEventListener('resize', adjustFontSize); adjustFontSize(); // 初始化

5.3 “点击开始没反应,控制台报错Uncaught ReferenceError: startPrize is not defined”——函数作用域污染

某次活动前测试,行政人员自己尝试给按钮加功能,在index.html底部写了<script>document.getElementById('start-btn').onclick = startPrize;</script>,结果报错。

原因分析startPrize函数定义在index.js里,而index.js是通过<script src>异步加载的。HTML底部的内联脚本执行时,index.js可能还没加载完,导致函数未定义。

安全写法:所有事件绑定必须在DOMContentLoaded事件里:

document.addEventListener('DOMContentLoaded', () => { document.getElementById('start-btn').onclick = startPrize; document.getElementById('stop-btn').onclick = stopPrize; });

我们在index.js开头就封装了这个逻辑,并加了防重复绑定保护:

if (typeof startPrize === 'function') { // 绑定逻辑 } else { console.error("❌ startPrize 函数未定义,请检查 index.js 是否正确加载"); }

5.4 常见问题速查表

问题现象可能原因快速排查步骤解决方案
页面空白,只显示背景图index.html<script>标签路径错误,或member.js文件损坏1. F12打开Console
2. 查看是否有Failed to load resource错误
3. 点击错误链接,确认文件是否存在
检查src属性路径(如./js/member.js是否应为js/member.js);用记事本重新保存member.js
中奖名字高亮但没追加到获奖区prize-history元素ID拼写错误,或CSS设置了display:none1. F12选中获奖区,看元素ID是否为prize-history
2. 在Elements面板搜索prize-history
确认HTML中<div id="prize-history">拼写;检查CSS是否有#prize-history { display: none; }
点击停止后名字还在滚动浏览器禁用了requestAnimationFrame(极罕见)或stopPrize函数被覆盖1. Console输入typeof stopPrize
2. 若返回undefined,说明函数未定义
重置index.js文件;确认未在其他地方用var stopPrize = ...重新声明
导出Excel按钮点击无反应浏览器禁用弹窗,或文件系统权限不足(仅限Edge旧版)1. 点击按钮时看浏览器地址栏是否有弹窗拦截图标
2. 尝试换Chrome浏览器
点击拦截图标允许弹窗;或手动复制获奖名单文本到Excel

最后分享一个小技巧:每次年会前,我会让行政人员做一次“压力测试”——打开index.html,快速连续点击“开始→停止”10次,观察是否卡顿、是否漏掉中奖记录。如果一切正常,现场基本零故障。这个动作耗时30秒,却能规避80%的突发状况。

本文还有配套的精品资源,点击获取

简介:直接双击index.html就能用的年会抽奖页面,完全跑在浏览器里,不用装服务器、不连后台、不传数据。所有参与人名字写在member.js里,打开文件删增改名就能更新名单,保存后刷新网页立即生效。点‘开始抽奖’按钮,姓名快速滚动,再点‘停止’就定格中奖者,名字自动高亮并追加到下方获奖区,支持一轮轮连续抽,历史结果一直保留在页面上不消失。配好了节日感背景图(bg.png、bg2.png)、奖状样式(borad.png)、提示图标(alert.png、alert2.png)和手写风字体(xk.ttf),logo.ico和logo.png可替换成公司标识。tagcanvas.js负责让标题文字带动态旋转效果,增强现场氛围。README.md写了三步操作说明:改名单、换Logo、本地打开,行政、HR或活动执行人员拿到就能上手,适合内网、投影仪或笔记本单机使用。


本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 23:33:37

如何在Windows上高效读写Btrfs分区:实用跨平台文件系统指南

如何在Windows上高效读写Btrfs分区&#xff1a;实用跨平台文件系统指南 【免费下载链接】btrfs WinBtrfs - an open-source btrfs driver for Windows 项目地址: https://gitcode.com/gh_mirrors/bt/btrfs 你是否需要在Windows系统中访问Linux Btrfs分区中的文件&#x…

作者头像 李华
网站建设 2026/6/10 23:30:40

如何免费获取思源宋体CN完整字体包:专业级中文排版终极指南

如何免费获取思源宋体CN完整字体包&#xff1a;专业级中文排版终极指南 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为商业项目寻找高质量中文字体而支付高昂授权费&#xff1f…

作者头像 李华
网站建设 2026/6/10 23:26:14

Windows 10系统瘦身专家:用开源命令行工具实现极致优化

Windows 10系统瘦身专家&#xff1a;用开源命令行工具实现极致优化 【免费下载链接】Win10BloatRemover Configurable CLI tool to easily and aggressively debloat and tweak Windows 10 by removing preinstalled UWP apps, services and more. Originally based on the W10…

作者头像 李华
网站建设 2026/6/10 23:26:12

Kinetis K10低功耗设计实战:从Cortex-M4内核到外设配置全解析

1. 项目概述&#xff1a;为什么选择Kinetis K10这颗“瑞士军刀”&#xff1f;在嵌入式开发的世界里&#xff0c;选型往往是项目成功的第一步。面对市场上琳琅满目的微控制器&#xff0c;工程师们常常在性能、功耗、外设和成本之间反复权衡。几年前&#xff0c;我在设计一个工业…

作者头像 李华
网站建设 2026/6/10 23:24:31

零成本启动的安全生产月巡检工具,安全检查 + 隐患上报一步到位

6 月安全生产月&#xff0c;很多企业面临这样的困境&#xff1a;安全生产月任务重、人手紧张&#xff0c;想上系统却预算有限&#xff1b;继续用纸质表&#xff0c;又怕检查走过场、隐患无人跟进。一、安全生产月的三大 “隐藏问题”问题 1&#xff1a;检查表不统一&#xff0c…

作者头像 李华