告别选择困难:原生JS实现“中午吃什么”随机抽签工具
面对“中午吃什么”这一终极难题,本文将介绍一款基于纯原生HTML、CSS和JavaScript开发的前端小工具。该工具无需任何框架依赖,具备菜品滚动抽签动画、自定义管理及本地数据持久化功能,双击即可运行,既适合解决日常用餐纠结,也适宜前端新手作为实战练手项目。
核心功能亮点
- 抽签滚动动画:菜品名称快速闪动,最终平稳定格,模拟真实抽奖体验。
- 完整菜品管理:支持用户自定义添加或删除菜品,灵活配置菜单。
- 本地数据持久化:利用localStorage存储数据,刷新或关闭页面后内容不丢失。
- 交互防抖处理:抽签过程中禁用按钮,防止重复触发导致逻辑错误。
- 极简响应式设计:UI自适应移动端与电脑端,视觉清爽简洁。
完整代码实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>中午吃什么</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui, sans-serif;}
body{
min-height:100vh;
background: linear-gradient(135deg,#fff7e6,#ffe8cc);
display:flex;flex-direction:column;align-items:center;justify-content:center;
padding:20px;
}
.card{
background:#fff;border-radius:20px;padding:30px;
box-shadow:0 8px 24px rgba(0,0,0,0.08);
max-width:460px;width:100%;
}
h2{color:#333;margin-bottom:20px;font-size:24px;text-align:center;}
.result{
font-size:34px;font-weight:bold;color:#e67700;min-height:50px;margin:20px 0;text-align:center;
}
.btn{
background:#ff9500;color:#fff;border:none;border-radius:12px;
padding:13px 28px;font-size:17px;cursor:pointer;transition:0.2s;
}
.btn:hover{background:#e68600;transform: translateY(-2px);}
.btn:disabled{background:#ffbc69;cursor:not-allowed;transform:none;}
.btn-wrap{text-align:center;margin-bottom:20px;}
.add-row{display:flex;gap:8px;margin-bottom:16px;}
#addInput{
flex:1;padding:11px;border:1px solid #ddd;border-radius:10px;font-size:15px;
}
.add-btn{
background:#2b85e4;color:white;border:none;border-radius:10px;padding:0 16px;cursor:pointer;
}
.food-list{
max-height:220px;overflow-y:auto;border:1px solid #eee;border-radius:10px;padding:10px;
}
.food-item{
display:flex;justify-content:space-between;align-items:center;
padding:8px 6px;border-bottom:1px solid #f3f3f3;
}
.food-item:last-child{border-bottom:none;}
.del-btn{
color:#f54b4b;background:none;border:none;cursor:pointer;font-size:14px;
}
</style>
</head>
<body>
<div class="card">
<h2>中午吃什么?</h2>
<div class="result" id="foodResult">点击抽签</div>
<div class="btn-wrap">
<button class="btn" id="randomBtn">开始抽签!</button>
</div>
<div class="add-row">
<input id="addInput" placeholder="输入菜品,点击添加">
<button class="add-btn" id="addBtn">添加</button>
</div>
<div class="food-list" id="foodListBox"></div>
</div>
<script>
const defaultFood = ['黄焖鸡','麻辣烫','兰州拉面','水饺','盖浇饭','螺蛳粉','牛肉汤','烤肉饭','汉堡','馄饨','炒菜米饭','米线'];
let foodList = [];
const randomBtn = document.getElementById('randomBtn');
const foodResult = document.getElementById('foodResult');
const addInput = document.getElementById('addInput');
const addBtn = document.getElementById('addBtn');
const foodListBox = document.getElementById('foodListBox');
// 读取本地存储
function loadData(){
const save = localStorage.getItem("foodList");
if(save){
foodList = JSON.parse(save);
}else{
foodList = [...defaultFood];
}
}
function saveData(){
localStorage.setItem("foodList", JSON.stringify(foodList));
}
// 渲染菜品列表
function renderList(){
foodListBox.innerHTML = '';
foodList.forEach((item,index)=>{
const div = document.createElement('div');
div.className = "food-item";
div.innerHTML = `<span>${item}</span><button class="del-btn" data-i="${index}">删除</button>`;
foodListBox.appendChild(div);
})
}
// 删除菜品
foodListBox.addEventListener('click',e=>{
if(e.target.classList.contains('del-btn')){
const idx = Number(e.target.dataset.i);
foodList.splice(idx,1);
saveData();
renderList();
}
})
// 添加菜品
addBtn.onclick = ()=>{
const val = addInput.value.trim();
if(!val) return;
foodList.push(val);
saveData();
renderList();
addInput.value = '';
}
addInput.addEventListener('keydown',e=>{
if(e.key === 'Enter') addBtn.click();
})
// 抽签闪动动画
randomBtn.onclick = function(){
if(foodList.length === 0){
foodResult.innerText = "请添加菜品!";
return;
}
randomBtn.disabled = true;
let count = 0;
const maxLoop = 30;
const timer = setInterval(()=>{
const rndIdx = Math.floor(Math.random() * foodList.length);
foodResult.innerText = foodList[rndIdx];
count++;
if(count >= maxLoop){
clearInterval(timer);
const finalIdx = Math.floor(Math.random() * foodList.length);
foodResult.innerText = foodList[finalIdx];
randomBtn.disabled = false;
}
},80)
}
loadData();
renderList();
</script>
</body>
</html>
技术实现解析
数据持久化机制
工具利用浏览器的localStorage API进行数据存储。由于localStorage仅支持字符串格式,代码中通过JSON.stringify将数组转换为字符串存入,读取时再使用JSON.parse还原为数组对象。页面初始化时优先读取缓存数据,确保了用户自定义菜单在刷新或关闭页面后依然保留。
动态列表渲染策略
采用遍历数组动态生成DOM结构的方式渲染菜品列表。每次执行添加或删除操作后,清空容器并重新渲染整个列表视图。这种“数据驱动视图”的逻辑简洁明了,便于维护且易于理解。
抽签动画原理
动画效果通过setInterval定时器实现。在指定时间内快速随机抽取数组中的菜品名称并更新显示,营造出“闪动”视觉效果。当循环达到预定次数后,清除定时器并输出最终的随机结果,同时恢复按钮状态,模拟出真实的抽奖落地感。
该工具完全基于原生前端技术栈构建,涵盖了动画交互、数据管理及本地存储等核心能力,无需服务器部署,本地打开即可使用。不仅有效解决了日常用餐的选择困难症,也是前端开发者学习基础DOM操作与本地存储的优质实战案例。
#前端小工具

