For a small project I needed a wheel of fortune – also known as
prize wheel,
winning wheel and
roulette wheel, but I couldn't find anything that did it the way I wanted it. So I made my own with these specifications:
The result is shown here to the right. It has only two dependancies:
- jQuery: who can live without it...
- Please: to generate random colors for the sections.
Their's not much HTML needed. There's just:
- A button called btnSpin that calls the function spin(),
- A canvas called Wheel,
- An arrow left of the canvas to mark the selected name.
Note that the code is part of an ASP.net MVC project. The first script line gets the names for the wheel from the model. The script:
var names = @Html.Raw(Json.Encode(Model.Names)); //Creates a javascript array from a C# list.
var n = names.length;
var colors = []; //Randomized based on 'n' further on.
var fps = 1000 / 30; //Animation speed.
var angle = 0;
var velocity = 1; //For the slowing down of the rotation.
var variation = 0; //So it doesn't always stop at the same name.
$(function () {
colors = Please.make_color({ colors_returned: n });
draw(angle);
$('#btnSpin').removeAttr('disabled');
})
function random(min, max) {
return Math.random() * (max - min) + min;
}
function getFontSize() { //The more names, the smaller they are displayed.
if (n < 10)
return 28;
else if(n < 20)
return 18;
else
return 10;
}
function spin() {
variation = random(0.15, 0.25);
window.setTimeout(animate, fps); //Begin the animation.
$('#btnSpin').attr('disabled', 'disabled');
}
function animate() { //Continue spinning until the wheel slowed down enough. Higher velocity means slower rotation.
velocity += variation * (velocity < 40 ? 1 : (velocity / 40));
draw(angle += Math.PI / velocity);
if (velocity > 200) {
velocity = 1;
$('#btnSpin').removeAttr('disabled');
}
else {
window.setTimeout(animate, fps);
}
}
function draw(angle) {
var canvas = document.getElementById("Wheel");
var ctx = canvas.getContext('2d');
var cx = canvas.width / 2;
var cy = canvas.height / 2;
var radius = cy - 5;
var range = Math.PI * 2;
var a = angle;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#222';
ctx.lineWidth = 3;
for (var i = 0; i < n; i++) {
ctx.save(); //Save the current state of the canvas.
ctx.translate(cx, cy);
ctx.rotate((a + (a + range / n)) / 2 + (range / 2) + (range / n / 2));
ctx.translate(-cx, -cy);
ctx.beginPath();
ctx.arc(2, cy, 6, 0, range);
ctx.closePath();
ctx.shadowColor = '#fff';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.fillStyle = '#222';
ctx.fill(); //Draws the handles on the outside of the wheel.
ctx.restore();
ctx.save();
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, a, a + range / n);
ctx.closePath();
ctx.fillStyle = colors[i];
ctx.fill(); //Draws the filled area of the sections.
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, a, a + range / n);
ctx.closePath();
ctx.stroke(); //Draws the outline of the sections.
ctx.translate(cx, cy);
ctx.rotate((a + (a + range / n)) / 2 + (range / 2));
ctx.translate(-cx, -cy);
ctx.beginPath();
ctx.rect(0, cy - 40, cx - 40, 80);
ctx.closePath();
ctx.clip(); //Makes sure the names don't draw outside their section.
ctx.fillStyle = '#222';
ctx.font = getFontSize() + 'px Verdana';
ctx.fillText(names[i], 20, cy + 10);
ctx.restore(); //Start fresh every time.
a += range / n;
}
ctx.beginPath();
ctx.arc(cx, cy, radius / 10, 0, range);
ctx.stroke();
ctx.fillStyle = '#444';
ctx.fill();
}