HTML+JavaScript积分抽奖系统
我将设计一个完整的积分抽奖系统,包含用户积分管理、抽奖转盘、中奖记录和积分消耗等功能。
设计思路
用户界面:显示当前积分、抽奖转盘、奖品列表
积分系统:模拟积分获取和消耗
抽奖逻辑:转盘旋转动画和随机奖品分配
中奖记录:保存并显示历史中奖信息
实现代码
<!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: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: #333;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
color: white;
}
h1 {
font-size: 2.8rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
}
.dashboard {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
flex: 1;
min-width: 280px;
}
.card h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
font-size: 1.6rem;
}
.points-display {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.points-value {
font-size: 3.5rem;
font-weight: bold;
color: #e74c3c;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
}
.points-label {
font-size: 1.2rem;
color: #7f8c8d;
}
.action-buttons {
display: flex;
gap: 15px;
margin-top: 15px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
flex: 1;
}
.btn-primary {
background: linear-gradient(to right, #3498db, #2980b9);
color: white;
}
.btn-success {
background: linear-gradient(to right, #2ecc71, #27ae60);
color: white;
}
.btn-warning {
background: linear-gradient(to right, #f39c12, #e67e22);
color: white;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.roulette-container {
position: relative;
width: 400px;
height: 400px;
margin: 0 auto 30px;
}
.roulette {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
overflow: hidden;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
background: #f8f9fa;
transition: transform 5s cubic-bezier(0.17, 0.67, 0.24, 0.99);
}
.roulette-slice {
position: absolute;
width: 50%;
height: 50%;
transform-origin: bottom right;
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.slice-content {
position: absolute;
width: 70%;
left: 0;
text-align: center;
transform-origin: center;
transform: rotate(45deg);
font-weight: 600;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.roulette-center {
position: absolute;
width: 80px;
height: 80px;
background: white;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #2c3e50;
cursor: pointer;
border: 5px solid #3498db;
}
.roulette-pointer {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 60px;
z-index: 20;
}
.pointer-triangle {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 30px solid #e74c3c;
}
.prize-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 15px;
margin-top: 15px;
}
.prize-item {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
text-align: center;
border-left: 5px solid #3498db;
transition: transform 0.3s;
}
.prize-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
}
.prize-name {
font-weight: 600;
margin-bottom: 5px;
color: #2c3e50;
}
.prize-points {
color: #e74c3c;
font-weight: bold;
}
.history-list {
max-height: 300px;
overflow-y: auto;
margin-top: 15px;
}
.history-item {
padding: 12px 15px;
background: #f8f9fa;
margin-bottom: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4px solid #2ecc71;
}
.history-prize {
font-weight: 600;
}
.history-date {
color: #7f8c8d;
font-size: 0.9rem;
}
.result-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 100;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 40px;
border-radius: 20px;
text-align: center;
max-width: 500px;
width: 90%;
animation: modalAppear 0.5s;
}
@keyframes modalAppear {
from { opacity: 0; transform: translateY(-50px); }
to { opacity: 1; transform: translateY(0); }
}
.result-icon {
font-size: 4rem;
margin-bottom: 20px;
}
.success-icon {
color: #2ecc71;
}
.fail-icon {
color: #e74c3c;
}
.result-text {
font-size: 1.8rem;
margin-bottom: 10px;
color: #2c3e50;
}
.result-details {
color: #7f8c8d;
margin-bottom: 25px;
font-size: 1.1rem;
}
.close-modal {
background: #3498db;
color: white;
border: none;
padding: 12px 30px;
border-radius: 50px;
font-size: 1rem;
cursor: pointer;
font-weight: 600;
}
footer {
text-align: center;
color: white;
margin-top: 40px;
opacity: 0.8;
font-size: 0.9rem;
}
@media (max-width: 768px) {
.dashboard {
flex-direction: column;
}
.roulette-container {
width: 300px;
height: 300px;
}
h1 {
font-size: 2.2rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>积分抽奖系统</h1>
<p class="subtitle">消耗积分参与抽奖,赢取丰富奖品</p>
</header>
<div class="dashboard">
<div class="card">
<h2>我的积分</h2>
<div class="points-display">
<div>
<div class="points-label">当前积分</div>
<div class="points-value" id="pointsValue">0</div>
</div>
<div>
<div class="points-label">已抽奖次数</div>
<div class="points-value" style="font-size: 2.5rem;" id="drawCount">0</div>
</div>
</div>
<p style="color: #7f8c8d; margin-bottom: 15px;">每次抽奖消耗 <strong>100积分</strong></p>
<div class="action-buttons">
<button class="btn btn-success" id="addPoints">获取积分 (+100)</button>
<button class="btn btn-warning" id="resetPoints">重置积分</button>
</div>
</div>
<div class="card">
<h2>抽奖转盘</h2>
<div class="roulette-container">
<div class="roulette" id="roulette">
<!-- 转盘切片通过JS动态生成 -->
</div>
<div class="roulette-pointer">
<div class="pointer-triangle"></div>
</div>
<div class="roulette-center" id="spinButton">
开始抽奖
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-primary" id="drawBtn" style="width: 200px;">消耗100积分抽奖</button>
</div>
</div>
</div>
<div class="dashboard">
<div class="card">
<h2>奖品列表</h2>
<div class="prize-list" id="prizeList">
<!-- 奖品列表通过JS动态生成 -->
</div>
</div>
<div class="card">
<h2>中奖记录</h2>
<div class="history-list" id="historyList">
<!-- 中奖记录通过JS动态生成 -->
</div>
<div class="action-buttons" style="margin-top: 20px;">
<button class="btn btn-warning" id="clearHistory">清空记录</button>
</div>
</div>
</div>
</div>
<!-- 抽奖结果弹窗 -->
<div class="result-modal" id="resultModal">
<div class="modal-content">
<div class="result-icon success-icon" id="resultIcon">🎁</div>
<div class="result-text" id="resultText">恭喜中奖!</div>
<div class="result-details" id="resultDetails">您获得了:一等奖</div>
<button class="close-modal" id="closeModal">确定</button>
</div>
</div>
<footer>
<p>积分抽奖系统 © 2023 | 每次抽奖消耗100积分</p>
</footer>
<script>
// 奖品数据
const prizes = [
{ id: 1, name: "一等奖", description: "高级蓝牙耳机", color: "#FF6B6B", probability: 5, pointsCost: 100 },
{ id: 2, name: "二等奖", description: "智能手环", color: "#4ECDC4", probability: 10, pointsCost: 100 },
{ id: 3, name: "三等奖", description: "移动电源", color: "#FFD166", probability: 15, pointsCost: 100 },
{ id: 4, name: "四等奖", description: "U盘 32GB", color: "#06D6A0", probability: 20, pointsCost: 100 },
{ id: 5, name: "五等奖", description: "咖啡券", color: "#118AB2", probability: 25, pointsCost: 100 },
{ id: 6, name: "谢谢参与", description: "下次好运", color: "#9D9D9D", probability: 25, pointsCost: 100 }
];
// 初始化数据
let userPoints = 500; // 初始积分
let drawCount = 0; // 抽奖次数
let isSpinning = false; // 转盘是否在旋转中
let history = []; // 中奖记录
// DOM元素
const pointsValueEl = document.getElementById('pointsValue');
const drawCountEl = document.getElementById('drawCount');
const rouletteEl = document.getElementById('roulette');
const spinButtonEl = document.getElementById('spinButton');
const drawBtn = document.getElementById('drawBtn');
const addPointsBtn = document.getElementById('addPoints');
const resetPointsBtn = document.getElementById('resetPoints');
const prizeListEl = document.getElementById('prizeList');
const historyListEl = document.getElementById('historyList');
const resultModal = document.getElementById('resultModal');
const resultText = document.getElementById('resultText');
const resultDetails = document.getElementById('resultDetails');
const resultIcon = document.getElementById('resultIcon');
const closeModalBtn = document.getElementById('closeModal');
const clearHistoryBtn = document.getElementById('clearHistory');
// 初始化函数
function init() {
updatePointsDisplay();
createRouletteSlices();
createPrizeList();
loadHistory();
updateHistoryDisplay();
// 绑定事件
spinButtonEl.addEventListener('click', startSpin);
drawBtn.addEventListener('click', startSpin);
addPointsBtn.addEventListener('click', addPoints);
resetPointsBtn.addEventListener('click', resetPoints);
closeModalBtn.addEventListener('click', closeResultModal);
clearHistoryBtn.addEventListener('click', clearHistory);
// 点击弹窗外关闭弹窗
resultModal.addEventListener('click', function(e) {
if (e.target === resultModal) {
closeResultModal();
}
});
}
// 更新积分显示
function updatePointsDisplay() {
pointsValueEl.textContent = userPoints;
drawCountEl.textContent = drawCount;
// 根据积分是否足够设置抽奖按钮状态
if (userPoints < 100 || isSpinning) {
drawBtn.disabled = true;
spinButtonEl.style.cursor = 'not-allowed';
} else {
drawBtn.disabled = false;
spinButtonEl.style.cursor = 'pointer';
}
}
// 创建转盘切片
function createRouletteSlices() {
rouletteEl.innerHTML = '';
const sliceAngle = 360 / prizes.length;
prizes.forEach((prize, index) => {
const slice = document.createElement('div');
slice.className = 'roulette-slice';
slice.style.backgroundColor = prize.color;
slice.style.transform = `rotate(${index * sliceAngle}deg)`;
const sliceContent = document.createElement('div');
sliceContent.className = 'slice-content';
sliceContent.style.transform = `rotate(${sliceAngle/2}deg)`;
sliceContent.innerHTML = `<div style="font-size: 16px; margin-bottom: 5px;">${prize.name}</div><div style="font-size: 12px;">${prize.description}</div>`;
slice.appendChild(sliceContent);
rouletteEl.appendChild(slice);
});
}
// 创建奖品列表
function createPrizeList() {
prizeListEl.innerHTML = '';
prizes.forEach(prize => {
const prizeItem = document.createElement('div');
prizeItem.className = 'prize-item';
prizeItem.style.borderLeftColor = prize.color;
prizeItem.innerHTML = `
<div class="prize-name">${prize.name}</div>
<div>${prize.description}</div>
<div class="prize-points">中奖概率: ${prize.probability}%</div>
`;
prizeListEl.appendChild(prizeItem);
});
}
// 添加积分
function addPoints() {
userPoints += 100;
updatePointsDisplay();
}
// 重置积分
function resetPoints() {
if (confirm("确定要重置积分吗?这将清空您的积分和抽奖次数。")) {
userPoints = 500;
drawCount = 0;
updatePointsDisplay();
}
}
// 开始抽奖
function startSpin() {
if (isSpinning) return;
// 检查积分是否足够
if (userPoints < 100) {
alert("积分不足!请获取更多积分。");
return;
}
// 扣除积分
userPoints -= 100;
drawCount++;
updatePointsDisplay();
// 设置旋转状态
isSpinning = true;
spinButtonEl.textContent = "抽奖中...";
spinButtonEl.style.pointerEvents = "none";
// 随机选择奖品(基于概率)
const randomNum = Math.random() * 100;
let cumulativeProbability = 0;
let selectedPrize = null;
for (const prize of prizes) {
cumulativeProbability += prize.probability;
if (randomNum <= cumulativeProbability) {
selectedPrize = prize;
break;
}
}
// 计算旋转角度(确保指针停在所选奖品上)
const prizeIndex = prizes.findIndex(p => p.id === selectedPrize.id);
const sliceAngle = 360 / prizes.length;
const targetRotation = 3600 + (360 - (prizeIndex * sliceAngle) - sliceAngle / 2);
// 应用旋转动画
rouletteEl.style.transform = `rotate(${targetRotation}deg)`;
// 抽奖结果
setTimeout(() => {
isSpinning = false;
spinButtonEl.textContent = "开始抽奖";
spinButtonEl.style.pointerEvents = "auto";
// 保存到历史记录
const historyItem = {
prize: selectedPrize.name,
description: selectedPrize.description,
date: new Date().toLocaleString()
};
history.unshift(historyItem);
saveHistory();
updateHistoryDisplay();
// 显示结果弹窗
showResultModal(selectedPrize);
}, 5000);
}
// 显示结果弹窗
function showResultModal(prize) {
resultText.textContent = prize.name === "谢谢参与" ? "很遗憾,未中奖" : "恭喜中奖!";
resultDetails.textContent = prize.name === "谢谢参与"
? "感谢参与,下次好运!"
: `您获得了:${prize.description}`;
resultIcon.textContent = prize.name === "谢谢参与" ? "😢" : "🎁";
resultIcon.className = prize.name === "谢谢参与" ? "result-icon fail-icon" : "result-icon success-icon";
resultModal.style.display = "flex";
}
// 关闭结果弹窗
function closeResultModal() {
resultModal.style.display = "none";
}
// 保存历史记录到本地存储
function saveHistory() {
localStorage.setItem('lotteryHistory', JSON.stringify(history));
localStorage.setItem('userPoints', userPoints);
localStorage.setItem('drawCount', drawCount);
}
// 加载历史记录
function loadHistory() {
const savedHistory = localStorage.getItem('lotteryHistory');
const savedPoints = localStorage.getItem('userPoints');
const savedDrawCount = localStorage.getItem('drawCount');
if (savedHistory) {
history = JSON.parse(savedHistory);
}
if (savedPoints) {
userPoints = parseInt(savedPoints);
}
if (savedDrawCount) {
drawCount = parseInt(savedDrawCount);
}
}
// 更新历史记录显示
function updateHistoryDisplay() {
historyListEl.innerHTML = '';
if (history.length === 0) {
historyListEl.innerHTML = '<div style="text-align: center; color: #7f8c8d; padding: 20px;">暂无抽奖记录</div>';
return;
}
history.forEach(item => {
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.innerHTML = `
<div>
<div class="history-prize">${item.prize}</div>
<div>${item.description}</div>
</div>
<div class="history-date">${item.date}</div>
`;
historyListEl.appendChild(historyItem);
});
}
// 清空历史记录
function clearHistory() {
if (history.length === 0) {
alert("暂无历史记录可清除!");
return;
}
if (confirm("确定要清空所有抽奖记录吗?")) {
history = [];
saveHistory();
updateHistoryDisplay();
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
功能说明
积分系统:
- 初始积分500分
- 每次抽奖消耗100积分
- 可以通过"获取积分"按钮增加积分
- 可以重置积分
抽奖转盘:
- 可视化转盘,包含6个不同奖品区域
- 旋转动画和物理效果模拟
- 基于概率的随机奖品分配
奖品设置:
- 6种奖品,概率不同(5%-25%)
- "谢谢参与"的概率为25%
历史记录:
- 保存所有抽奖记录
- 使用localStorage在浏览器中持久化存储
- 可以清空历史记录
响应式设计:
- 适应不同屏幕尺寸
- 在手机和桌面设备上都有良好显示效果
这个系统完全使用HTML、CSS和JavaScript实现,无需任何外部库或后端支持,可以直接在浏览器中运行。所有数据都保存在浏览器的localStorage中。