Creating a Roulette Wheel Using HTML5 Canvas
Join the DZone community and get the full member experience.
Join For FreeHTML5 is really hot all over the web right now so I figured I would drop some HTML5 knowledge on y'all. I have worked with Flash and Flex consistently for the last few years so I can easily drop and manipulate graphics in it, but I haven't done much with HTML5. This lead me to try and challenge myself to recreate something I built in Flash in HTML5. That is what we are going to look at and learn to build in this tutorial.
A few things should be mentioned. First IE simply doesn't implement the <canvas> tag which means it won't work in IE, Google has released Explorer Canvas which fixes this to some extent. I, however, didn't worry about adding that to this tutorial because this post is about creating the content. Also, there are some small things that don't work in other browsers. Finally, Mobile Safari (iPhone, iPod Touch, and iPad) doesn't implement text rendering correctly.
To get things rolling we'll first put some simple html down. This includes the <canvas> tag which is what we're using for this tutorial. The only other element we are going to use is an input button to spin our wheel. I also added a little bit of inline style to place the button.
<input type="button" value="spin" style="float:left;" />
<canvas id="canvas" width="500" height="500"></canvas>
The next thing we are going to do is begin drawing some stuff on to our canvas. This is going to be done in JavaScript in a function I named drawRouletteWheel. The basics of drawing on the <canvas> in 2D at least involve grabbing a drawing context and then drawing onto it. Sounds complicated doesn't it. Let's look at a little bit of code.
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200"];
var startAngle = 0;
var arc = Math.PI / 6;
var ctx;
function drawRouletteWheel() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var outsideRadius = 200;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
}
}
}
Okay, looking at the start of the function we grab a reference to our canvas object by id - nothing new here. Then we check to make sure the browser supports grabbing a drawing context, this is important to make sure we don't throw errors in browsers that don't support the features. Then we set a couple of variables for the inner and outer radius of our wheel. Now, we get into the meat. The first thing we do is grab a 2D drawing context by calling getContext. From here we clear the canvas, so we have a blank slate to draw onto. Then we set the stroke color and width which in this case is "black" and 2.
The next part takes a little bit of explaining, we are going to loop through 12 sections (the number of sides we are going to draw). For each section we determine the angle of where each section is going to start. The startAngle is a global variable in which we are going to use to animate the wheel, for now, it is set to 0. The following line sets the fill color by pulling a value from the global colors array. Drawing begins after that with starting a path drawing two arcs, using the arc(x, y, radius, startAngle, endAngle, anticlockwise) function. We then tell the context to stroke the path and fill the path, these will use the previously set parameters.
Now, we are going to add the restaurant text to our drawing code. Also, to finish off the drawing function we'll add the nice little arrow at the top. The text drawing is done using the fillText(text, x, y [, maxWidth ]) function. Let's take a look at the updated code.
var restaraunts = ["Wendy's", "McDonalds", "Chick-fil-a", "Five Guys",
"Gold Star", "La Mexicana", "Chipotle", "Tazza Mia",
"Panera", "Just Crepes", "Arby's", "Indian"];
function drawRouletteWheel() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px Helvetica, Arial';
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius,
250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = restaraunts[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
Looking at the new text drawing we start by saving the current context state - this is going to allow us to rotate and translate the text without affecting everything else. We then set some shadow stuff, which will put a drop shadow on the text. Translating and rotating the text is tackled next, we first translate the text to the correct placement on the wheel and then rotate it. Drawing the text follows this but with the added effect of centering the text by measuring it and dividing that by 2 to offset it. You'll notice we grab the restaurant name from a global array. Lastly we restore to our initial state we saved to, this makes sure the transformations do not affect later drawing. The arrow is even easier to explain, we just move to one corner and draw lines to create the shape and fill it with black.
Below I have the complete code for this demo. I'll explain the spinning and animating code right after.
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200"];
var restaraunts = ["Wendy's", "McDonalds", "Chick-fil-a", "Five Guys",
"Gold Star", "La Mexicana", "Chipotle", "Tazza Mia",
"Panera", "Just Crepes", "Arby's", "Indian"];
var startAngle = 0;
var arc = Math.PI / 6;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var ctx;
function drawRouletteWheel() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px Helvetica, Arial';
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius,
250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = restaraunts[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3 + 4 * 1000;
rotateWheel();
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
spinTimeout = setTimeout('rotateWheel()', 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 30px Helvetica, Arial';
var text = restaraunts[index]
ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
ctx.restore();
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
drawRouletteWheel();
It's a lot to take in but we'll take it slow. At the bottom of the code you'll see we call drawRouletteWheel, this is too draw the initial wheel when the page loads. We have to update the input button we put on the screen to now call our spin function, which handles spinning the wheel, of course. The updated input follows.
<input type="button" value="spin" onclick="spin();" style="float:left;" />
Taking a look at the spin function we set a couple global variables that decide how fast we are going to spin and how long we are going to spin. These have a little bit of randomness in them to make things more interesting. Lastly, we call rotateWheel.
The rotateWheel function updates the amount of time we have been spinning, checks to see if we should stop, updates spinning speed, draws the wheel, and then calls itself in 30 milliseconds. After checking the time we change the spinning angle, startAngle, this is done using an easing function to slow down the spinning. Then we call our drawing function and use setTimeout and keeps a reference to call our rotate again.
The last function we need look at is stopping the wheel, stopRotateWheel. This starts with clearing the timeout which will stop the code from rotating the wheel. Then we use some math to figure out what restaurant is at the top. Finally, we draw the large name of the of the selected restaurant in the middle of the wheel.
Well, that's pretty much a wrap on this post. I really like how HTML5 and the canvas tag are coming along. Although it's not quite ready for primetime production work yet. I look forward to the next couple years of web development.
If you have any questions on the tutorial or just want to bash on my coding skills feel free to drop a comment.
Published at DZone with permission of Charlie Key. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments