Multi-Category Spinner Wheel
Create custom spinner wheels for rewards, tasks, group themes, and more. Spin to randomly select from your categories.
Embed This Tool
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spinner Wheel</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f3f4f6; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; }
.container { max-width: 900px; width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
@media (max-width: 768px) { .container { grid-template-columns: 1fr; } }
.panel { background: white; border-radius: 16px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); padding: 24px; }
h2 { color: #1f2937; margin-bottom: 20px; font-size: 1.5rem; }
label { display: block; font-weight: 600; color: #374151; margin-bottom: 8px; }
select, input, textarea { width: 100%; padding: 12px; border: 2px solid #e5e7eb; border-radius: 8px; font-size: 16px; font-family: inherit; }
select:focus, input:focus, textarea:focus { outline: none; border-color: #9333ea; }
textarea { resize: vertical; min-height: 100px; }
button { width: 100%; padding: 14px 24px; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.2s; }
#saveCategoryBtn { background: #9333ea; color: white; }
#saveCategoryBtn:hover { background: #7e22ce; }
#spinBtn { background: #9333ea; color: white; font-size: 1.5rem; padding: 20px 40px; }
#spinBtn:hover { background: #7e22ce; }
#wheelCanvas { max-width: 100%; height: auto; }
#winnerDisplay { display: none; margin-top: 24px; background: linear-gradient(135deg, #9333ea, #7e22ce); border-radius: 12px; padding: 24px; text-align: center; }
#winnerName { color: white; font-size: 2rem; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="panel">
<h2>Configuration</h2>
<label>Select Category</label>
<select id="categorySelect">
<option value="0">Category 1</option>
<option value="1">Category 2</option>
<option value="2">Category 3</option>
</select>
<label style="margin-top: 16px;">Category Name</label>
<input type="text" id="categoryName" placeholder="e.g., Rewards">
<label style="margin-top: 16px;">Items (comma-separated)</label>
<textarea id="categoryItems" placeholder="e.g., Extra recess, Homework pass, Choose music"></textarea>
<button id="saveCategoryBtn" style="margin-top: 16px;">Save Category</button>
</div>
<div class="panel">
<h2 style="text-align: center;">Spinner Wheel</h2>
<div style="display: flex; justify-content: center; margin: 24px 0;">
<canvas id="wheelCanvas" width="400" height="400"></canvas>
</div>
<button id="spinBtn">Spin!</button>
<div id="winnerDisplay">
<p style="color: white; font-size: 1.2rem; margin-bottom: 8px;">The winner is...</p>
<h2 id="winnerName"></h2>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('wheelCanvas');
const ctx = canvas.getContext('2d');
const categorySelect = document.getElementById('categorySelect');
const categoryName = document.getElementById('categoryName');
const categoryItems = document.getElementById('categoryItems');
const saveCategoryBtn = document.getElementById('saveCategoryBtn');
const spinBtn = document.getElementById('spinBtn');
const winnerDisplay = document.getElementById('winnerDisplay');
const winnerName = document.getElementById('winnerName');
const colors = ['#9333ea', '#7e22ce', '#6b21a8', '#a855f7', '#c084fc', '#d8b4fe', '#e9d5ff', '#f3e8ff'];
let categories = [
{ name: 'Category 1', items: ['Item 1', 'Item 2', 'Item 3', 'Item 4'] },
{ name: 'Category 2', items: ['Item A', 'Item B', 'Item C', 'Item D'] },
{ name: 'Category 3', items: ['Option 1', 'Option 2', 'Option 3', 'Option 4'] }
];
let currentCategory = 0;
let rotation = 0;
let isSpinning = false;
// Load categories from localStorage
const savedCategories = localStorage.getItem('spinnerWheelCategories');
if (savedCategories) {
categories = JSON.parse(savedCategories);
}
function saveCategories() {
localStorage.setItem('spinnerWheelCategories', JSON.stringify(categories));
}
function drawWheel() {
const items = categories[currentCategory].items;
const numSegments = items.length;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const segmentAngle = (2 * Math.PI) / numSegments;
for (let i = 0; i < numSegments; i++) {
const startAngle = rotation + i * segmentAngle;
const endAngle = startAngle + segmentAngle;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.closePath();
ctx.fillStyle = colors[i % colors.length];
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.stroke();
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(startAngle + segmentAngle / 2);
ctx.textAlign = 'right';
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px Arial';
ctx.fillText(items[i], radius - 10, 5);
ctx.restore();
}
// Draw pointer
ctx.beginPath();
ctx.moveTo(centerX + radius + 10, centerY);
ctx.lineTo(centerX + radius - 10, centerY - 15);
ctx.lineTo(centerX + radius - 10, centerY + 15);
ctx.closePath();
ctx.fillStyle = '#1f2937';
ctx.fill();
}
function updateInputs() {
categoryName.value = categories[currentCategory].name;
categoryItems.value = categories[currentCategory].items.join(', ');
}
categorySelect.addEventListener('change', (e) => {
currentCategory = parseInt(e.target.value);
updateInputs();
drawWheel();
});
// Auto-save category name as you type
categoryName.addEventListener('input', () => {
categories[currentCategory].name = categoryName.value || 'Category ' + (currentCategory + 1);
saveCategories();
});
// Auto-save category items as you type
categoryItems.addEventListener('input', () => {
const items = categoryItems.value.split(',').map(item => item.trim()).filter(item => item.length > 0);
if (items.length > 0) {
categories[currentCategory].items = items;
saveCategories();
drawWheel();
}
});
saveCategoryBtn.addEventListener('click', () => {
categories[currentCategory].name = categoryName.value || 'Category ' + (currentCategory + 1);
categories[currentCategory].items = categoryItems.value.split(',').map(item => item.trim()).filter(item => item.length > 0);
if (categories[currentCategory].items.length === 0) {
categories[currentCategory].items = ['Item 1', 'Item 2', 'Item 3'];
}
saveCategories();
drawWheel();
});
spinBtn.addEventListener('click', () => {
if (isSpinning) return;
isSpinning = true;
winnerDisplay.style.opacity = '0';
const items = categories[currentCategory].items;
const spinDuration = 3000;
const startRotation = rotation;
const spinAmount = Math.random() * 2 * Math.PI + 10 * Math.PI;
const startTime = Date.now();
function animate() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / spinDuration, 1);
const easeOut = 1 - Math.pow(1 - progress, 3);
rotation = startRotation + spinAmount * easeOut;
drawWheel();
if (progress < 1) {
requestAnimationFrame(animate);
} else {
isSpinning = false;
const segmentAngle = (2 * Math.PI) / items.length;
const normalizedRotation = rotation % (2 * Math.PI);
const winningIndex = Math.floor((2 * Math.PI - normalizedRotation) / segmentAngle) % items.length;
winnerName.textContent = items[winningIndex];
winnerDisplay.style.opacity = '1';
}
}
animate();
});
updateInputs();
drawWheel();
</script>
</body>
</html>