Quay số
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Ứng dụng Quay Số Trúng Thưởng</title>
<style>
/* Reset & cơ bản */
body {
font-family: Arial, sans-serif;
background: #f4f4f4;
margin: 0;
padding: 20px;
}
h1, h2 {
text-align: center;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.controls, .wheel-container, .history {
background: #fff;
padding: 15px;
margin: 10px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.controls {
flex: 1 1 300px;
max-width: 350px;
}
.wheel-container {
flex: 1 1 400px;
text-align: center;
}
.history {
flex: 1 1 300px;
max-height: 300px;
overflow-y: auto;
}
canvas {
border: 2px solid #333;
border-radius: 50%;
background: #eee;
}
.result {
margin-top: 15px;
font-size: 1.2em;
font-weight: bold;
text-align: center;
color: #007700;
}
input[type="text"] {
width: 80%;
padding: 5px;
margin-bottom: 10px;
}
input[type="range"] {
width: 100%;
}
button {
padding: 7px 15px;
margin: 5px 0;
cursor: pointer;
}
.prize-list {
list-style: none;
padding: 0;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ccc;
margin-bottom: 10px;
}
.prize-list li {
padding: 5px;
border-bottom: 1px dashed #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.prize-list li:last-child {
border-bottom: none;
}
.prize-list button {
font-size: 0.8em;
margin-left: 5px;
}
/* Hiệu ứng nhấp nháy cho phần thắng */
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.2; }
100% { opacity: 1; }
}
</style>
</head>
<body>
<h1>Ứng dụng Quay Số Trúng Thưởng</h1>
<div class="container">
<!-- Phần điều khiển nhập đối tượng và cài đặt thời gian quay -->
<div class="controls">
<h2>Quản lý Đối tượng Quay</h2>
<input type="text" id="prizeInput" placeholder="Nhập đối tượng/quà thưởng">
<button id="addPrizeBtn">Thêm đối tượng</button>
<ul id="prizeList" class="prize-list"></ul>
<h2>Thiết lập Thời gian Quay</h2>
<label for="spinTimeSlider">Thời gian quay (giây): <span id="spinTimeDisplay">5</span></label>
<input type="range" id="spinTimeSlider" min="5" max="8" step="0.1" value="5">
<br>
<button id="spinBtn">Quay Ngay!</button>
<div class="result" id="resultDisplay"></div>
</div>
<!-- Phần hiển thị bánh xe quay -->
<div class="wheel-container">
<h2>Bánh Xe May Mắn</h2>
<canvas id="wheelCanvas" width="400" height="400"></canvas>
</div>
<!-- Lịch sử quay -->
<div class="history">
<h2>Lịch Sử Quay</h2>
<ul id="historyList"></ul>
</div>
</div>
<!-- Các thẻ âm thanh: Hãy thay đổi src nếu cần -->
<audio id="tickSound" src="tick.mp3"></audio>
<audio id="winSound" src="win.mp3"></audio>
<script>
(function() {
// Các biến toàn cục
let prizes = []; // Danh sách đối tượng/quà thưởng (dạng string)
const canvas = document.getElementById('wheelCanvas');
const ctx = canvas.getContext('2d');
const spinBtn = document.getElementById('spinBtn');
const spinTimeSlider = document.getElementById('spinTimeSlider');
const spinTimeDisplay = document.getElementById('spinTimeDisplay');
const resultDisplay = document.getElementById('resultDisplay');
const prizeInput = document.getElementById('prizeInput');
const addPrizeBtn = document.getElementById('addPrizeBtn');
const prizeList = document.getElementById('prizeList');
const historyList = document.getElementById('historyList');
const tickSound = document.getElementById('tickSound');
const winSound = document.getElementById('winSound');
let currentAngle = 0; // Góc hiện tại của bánh xe (radians)
let targetAngle = 0; // Góc mục tiêu sau khi quay
let initialAngle = 0; // Góc ban đầu trước khi quay
let startTime = null; // Thời điểm bắt đầu quay (timestamp)
let spinDuration = 0; // Thời gian quay (giây) do slider chỉ định
let spinning = false; // Cờ cho biết đang quay hay không
let winningIndex = null; // Vị trí đối tượng trúng thưởng
let lastTickIndex = -1; // Để phát âm thanh tick mỗi khi thay đổi phần
// Cập nhật giá trị hiển thị của slider
spinTimeSlider.addEventListener('input', function() {
spinTimeDisplay.textContent = spinTimeSlider.value;
});
// Cập nhật danh sách đối tượng/quà thưởng trong giao diện
function updatePrizeList() {
prizeList.innerHTML = '';
prizes.forEach((prize, index) => {
const li = document.createElement('li');
li.textContent = prize;
// Nút Sửa
const editBtn = document.createElement('button');
editBtn.textContent = "Sửa";
editBtn.onclick = function() {
const newVal = prompt("Sửa đối tượng", prize);
if (newVal !== null && newVal.trim() !== "") {
prizes[index] = newVal.trim();
updatePrizeList();
drawWheel();
}
};
// Nút Xóa
const delBtn = document.createElement('button');
delBtn.textContent = "Xóa";
delBtn.onclick = function() {
prizes.splice(index, 1);
updatePrizeList();
drawWheel();
};
li.appendChild(editBtn);
li.appendChild(delBtn);
prizeList.appendChild(li);
});
}
addPrizeBtn.addEventListener('click', function() {
const val = prizeInput.value.trim();
if (val !== "") {
prizes.push(val);
prizeInput.value = "";
updatePrizeList();
drawWheel();
}
});
// Hàm vẽ bánh xe quay dựa trên danh sách đối tượng
// Nếu truyền highlight=true và blink=true, thì đối tượng trúng thưởng sẽ được highlight nhấp nháy
function drawWheel(highlight = false, blink = false) {
if (prizes.length === 0) {
// Nếu không có đối tượng nào, xóa canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
return;
}
const numSlices = prizes.length;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 10;
const sliceAngle = 2 * Math.PI / numSlices;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Vẽ các mảnh bánh xe
for (let i = 0; i < numSlices; i++) {
const startAngle = i * sliceAngle;
const endAngle = startAngle + sliceAngle;
ctx.beginPath();
// Lưu ý: cộng currentAngle để xoay bánh xe
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle + currentAngle, endAngle + currentAngle);
ctx.closePath();
// Màu sắc: dùng HSL để phân phối màu đều
ctx.fillStyle = `hsl(${i * 360 / numSlices}, 70%, 60%)`;
ctx.fill();
// Nếu đang highlight và đây là phần trúng thưởng, vẽ viền nhấp nháy
if (highlight && i === winningIndex && blink) {
ctx.strokeStyle = "red";
ctx.lineWidth = 5;
ctx.stroke();
} else {
ctx.strokeStyle = "#fff";
ctx.lineWidth = 2;
ctx.stroke();
}
// Vẽ nhãn đối tượng
ctx.save();
ctx.translate(centerX, centerY);
// Xoay đến giữa mảnh bánh xe
const textAngle = startAngle + sliceAngle / 2 + currentAngle;
ctx.rotate(textAngle);
ctx.textAlign = "right";
ctx.fillStyle = "#000";
ctx.font = "16px Arial";
ctx.fillText(prizes[i], radius - 10, 5);
ctx.restore();
}
// Vẽ hình tròn ở trung tâm
ctx.beginPath();
ctx.arc(centerX, centerY, 30, 0, 2 * Math.PI);
ctx.fillStyle = "#fff";
ctx.fill();
ctx.strokeStyle = "#000";
ctx.stroke();
// Vẽ mũi tên chỉ (ở phía trên bánh xe)
ctx.beginPath();
ctx.moveTo(centerX, centerY - radius - 20);
ctx.lineTo(centerX - 15, centerY - radius - 5);
ctx.lineTo(centerX + 15, centerY - radius - 5);
ctx.closePath();
ctx.fillStyle = "black";
ctx.fill();
}
// Hàm animateSpin: dùng requestAnimationFrame để cập nhật chuyển động bánh xe
function animateSpin(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = (timestamp - startTime) / 1000; // tính theo giây
let t = elapsed / spinDuration;
if (t > 1) t = 1;
// Sử dụng hàm easing (easeOutCubic) để tạo chuyển động mượt dần
const eased = 1 - Math.pow(1 - t, 3);
currentAngle = initialAngle + (targetAngle - initialAngle) * eased;
drawWheel();
// Phát âm thanh tick khi bánh xe qua từng phần:
const numSlices = prizes.length;
const sliceAngle = 2 * Math.PI / numSlices;
let pointerAngle = (-Math.PI / 2 - currentAngle) % (2 * Math.PI);
if (pointerAngle < 0) pointerAngle += 2 * Math.PI;
const currentTickIndex = Math.floor(pointerAngle / sliceAngle);
if (currentTickIndex !== lastTickIndex) {
lastTickIndex = currentTickIndex;
if (tickSound) {
tickSound.currentTime = 0;
tickSound.play();
}
}
if (t < 1) {
requestAnimationFrame(animateSpin);
} else {
// Quay hoàn tất
spinning = false;
startTime = null;
initialAngle = currentAngle;
if (winSound) {
winSound.currentTime = 0;
winSound.play();
}
// Xác định đối tượng trúng thưởng dựa trên winningIndex đã chọn trước
const winningPrize = prizes[winningIndex];
resultDisplay.textContent = `Trúng: ${winningPrize} (Thời gian quay: ${spinDuration} giây)`;
// Lưu kết quả vào lịch sử
const li = document.createElement('li');
const now = new Date();
li.textContent = `${now.toLocaleTimeString()} - ${winningPrize} (${spinDuration} giây)`;
historyList.prepend(li);
// Bắt đầu hiệu ứng nhấp nháy highlight phần thắng
startBlinking();
spinBtn.disabled = false;
}
}
// Hàm quay bánh xe: tính toán góc quay mục tiêu dựa trên đối tượng trúng thưởng ngẫu nhiên
function spinWheel() {
if (spinning || prizes.length === 0) return;
spinning = true;
resultDisplay.textContent = "";
spinBtn.disabled = true;
lastTickIndex = -1;
spinDuration = parseFloat(spinTimeSlider.value);
initialAngle = currentAngle;
// Chọn ngẫu nhiên chỉ số đối tượng trúng thưởng
winningIndex = Math.floor(Math.random() * prizes.length);
// Chọn số vòng quay ngẫu nhiên (ví dụ 4 hoặc 5 vòng)
const rounds = Math.floor(Math.random() * 2) + 4;
const sliceAngle = 2 * Math.PI / prizes.length;
// Tính góc mục tiêu sao cho tâm của phần trúng thưởng (winningIndex) sẽ nằm ngay dưới mũi tên
// Công thức: targetAngle = -π/2 - (winningIndex + 0.5)*sliceAngle + rounds*2π
targetAngle = -Math.PI / 2 - (winningIndex + 0.5) * sliceAngle + rounds * 2 * Math.PI;
startTime = null;
requestAnimationFrame(animateSpin);
}
spinBtn.addEventListener('click', spinWheel);
// Hàm hiệu ứng nhấp nháy cho phần thắng
function startBlinking() {
let blink = false;
let blinkCount = 0;
const blinkInterval = setInterval(() => {
blink = !blink;
drawWheel(true, blink);
blinkCount++;
if (blinkCount > 6) { // Hiệu ứng nhấp nháy khoảng 3 giây (mỗi 500ms)
clearInterval(blinkInterval);
drawWheel();
}
}, 500);
}
// Vẽ bánh xe ban đầu (nếu có đối tượng)
drawWheel();
})();
</script>
</body>
</html>