HTML5 is really hot all over the web right now so I figured I would dropsome HTML5 knowledge on y'all. I have worked with Flash and Flexconsistently for the last few years so I can easily drop and manipulategraphics in it, but I haven't done much with HTML5. This lead me to tryand 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 thistutorial.
A few things should be mentioned. First IE simply doesn't implement thecanvas
tag which means it won't work in IE, Google has released ExplorerCanvas which fixes this tosome extent. I, however, didn't worry about adding that to this tutorialbecause this post is about creating the content. Also, there are somesmall things that don't work in other browsers. Finally, Mobile Safari(iPhone, iPod Touch, and iPad) doesn't implement text renderingcorrectly.
To get things rolling we'll first put some simple html down. Thisincludes thecanvas
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.
The next thing we are going to do is begin drawing some stuff on to ourcanvas. This is going to be done in JavaScript in a function I nameddrawRouletteWheel
. The basics of drawing on the canvas
in 2D at least involve grabbing a drawing context and then drawing ontoit. 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 ourcanvas object by id - nothing new here. Then we check to make sure thebrowser supports grabbing a drawing context, this is important to makesure 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 ourwheel. Now, we get into the meat. The first thing we do is grab a 2Ddrawing context by callinggetContext. 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" and2.
The next part takes a little bit of explaining, we are going to loopthrough 12 sections (the number of sides we are going to draw). For eachsection 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 toanimate the wheel, for now, it is set to 0. The following line sets thefill color by pulling a value from the global colors array. Drawingbegins 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 atthe top. The text drawing is done using thefillText(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 contextstate - this is going to allow us to rotate and translate the textwithout affecting everything else. We then set some shadow stuff, whichwill put a drop shadow on the text. Translating and rotating the text istackled next, we first translate the text to the correct placement onthe wheel and then rotate it. Drawing the text follows this but with theadded effect of centering the text by measuring it and dividing that by2 to offset it. You'll notice we grab the restaurant name from a globalarray. Lastly we restore to our initial state we saved to, this makessure the transformations do not affect later drawing. The arrow is eveneasier to explain, we just move to one corner and draw lines to createthe shape and fill it with black.
Below I have the complete code for this demo. I'll explain the spinningand 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 codeyou'll see we call
drawRouletteWheel
, this is too draw the initial wheel when the page loads. We have toupdate the input button we put on the screen to now call our spinfunction, which handles spinning the wheel, of course. The updated inputfollows.
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 aregoing to spin and how long we are going to spin. These have a little bitof 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 seeif we should stop, updates spinning speed, draws the wheel, and thencalls itself in 30 milliseconds. After checking the time we change thespinning angle, startAngle
, this is done using an easing function to slow down the spinning. Thenwe call our drawing function and usesetTimeoutand 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 fromrotating the wheel. Then we use some math to figure out what restaurantis at the top. Finally, we draw the large name of the of the selectedrestaurant in the middle of the wheel.
Well, that's pretty much a wrap on this post. I really like how HTML5and the canvas tag are coming along. Although it's not quite ready forprimetime production work yet. I look forward to the next couple yearsof web development.
Source Files:
- roulettewheel.html