Q: Can you make animations with HTML5?
A: You certainly can!
This tutorial assumes basic knowledge of:
Since HTML5 Introduces the <canvas>
element, you can draw 2D raster images right in the browser. By raster, I mean it draws in a set it and forget it style, whereas vector graphics are where objects that are drawn are kept in memory. Naturally, raster is faster than vector, but a lot lower level. If you're used to vector (Adobe Flash, for instance), then you're going to have to re-learn how to think about animation.
Let's start with an example
Of course, start with a <canvas>
element
<canvas id="canvas"></canvas>
Quick tip: when you put javascript below the body of the document (or at least the element it's working with), there is no need to call
getElementByID()
. In other words, a global reference to "canvas" is made when I give the canvas an id "canvas". If you were to put "snuffaluffagus" as the id (<div id="snuffaluffagus"</div>
), then you'd be able to reference it in JS viawindow.snuffaluffagus
, or since window is the global scope, simplysnuffaluffagus
.
Then, in JavaScript
(function(){
var ctx;
canvas.width = 500;
canvas.height = 500;
ctx = canvas.getContext('2d');
ctx.fillRect(0,0,100,100);
})();
This will create the following:
Yawn. Boooooring, right? Let's get it animated!
function render(context, obj){
context.fillRect(obj.x, obj.y, obj.w, obj.h);
}
function Square(x,y,w,h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.run = function(context){
this.x += 2;
render(context, this);
}
};
// That semicolon above is very important. If you remove it, it will try to call the function definition above using the enclosure below as an argument. In other words, you'll get an error
// So why am I using an enclosure / Anonymous function? Because it runs faster.
(function(){
var ctx,
square = new Square(0,0,100,100);
canvas = canvas2;
canvas.width = 600;
canvas.height = 400;
ctx = canvas.getContext('2d');
(function loop(){
ctx.clearRect(0,0,600,400);
square.run(ctx);
setTimeout(loop, 1000/60);
})();
})();
What I did was added a class (well, technically it's a constructor function) called Square, and introduced a delayed loop. It's running at 1000 / 60
milliseconds (or 60 FPS) between each re-render.
Note: You might have to refresh the page due to the animations finishing before you can can scroll down to see them
Caution: don't forget to clear the screen on each cycle of the loop, or else you'll get this:
Now what if I want multiple squares moving at different speeds?
function render(context, obj){
context.fillRect(obj.x, obj.y, obj.w, obj.h);
}
function Square(x,y,w,h,speed){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speed = speed;
this.run = function(context){
this.x += this.speed;
render(context, this);
}
};
(function(){
var ctx,
squares = [];
for(var i = 0; i < 12; i++){
squares.push(new Square(0,i*10,10,10,Math.random()*5));
}
canvas.width = 600;
canvas.height = 400;
ctx = canvas.getContext('2d');
(function loop(){
ctx.clearRect(0,0,600,400);
for(var i = 0; i < squares.length; i++){
squares[i].run(ctx);
}
setTimeout(loop, 1000/60);
})();
})();
That's neat and all, but how am I supposed to animate something abstract? Like a camera shaking, or like an animation someone would actually watch?
To do that kind of animation, you'll have to be ready to edit data in arrays. Manually defined arrays.
The idea is that there's an array holding the data for each frame of the animation, and a pointer used to scrub through it. Once per frame, the number is incremented, and voila, you have a working animation.
Quick Tip: if you use
array[ptr++]
, you can skip the step of incrementingptr
. The reason this works lies in the difference between++num
(prefixed increment) andnum++
(postfixed increment). In a prefixed increment, the operation is done first, then the value is returned. That is, num increments by 1, and then returns the new value. In a postfixed increment, the operation is done after the return. The value of num is returned, and then the operation is done.var a = 5, b = 5; alert(++a); // 6 alert(a); // 6 alert(b++); // 5 alert(b); // 6
function render(context, obj){
context.fillRect(obj.x, obj.y, obj.w, obj.h);
}
function Square(x,y,w,h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
var anim = [
/* x y width height */
/*---------------------------------*/
[ 0, 0, 50, 50 ],
[ 20, 0, 50, 50 ],
[ 40, 20, 50, 50 ],
[ 60, 40, 50, 50 ],
[ 80, 60, 50, 50 ],
[ 80, 80, 50, 50 ],
[ 60, 100, 50, 50 ],
[ 40, 120, 50, 50 ],
[ 20, 140, 50, 50 ],
[ 20, 140, 30, 30 ],
[ 20, 140, 10, 10 ],
[ 20, 14, -10, -10 ],
[ 20, 12, 10, 10 ],
[ 20, 10, 30, 30 ],
[ 20, 80, 50, 50 ],
[ 20, 60, 50, 50 ],
[ 20, 40, 50, 50 ],
[ 20, 20, 50, 50 ],
[ 15, 15, 50, 50 ],
[ 10, 10, 50, 50 ],
[ 5, 5, 50, 50 ],
[ 0, 0, 50, 50 ],
];
var anim_ptr = 0;
this.run = function(context){
var frame = anim[anim_ptr];
this.x = anim[anim_ptr][0];
this.y = anim[anim_ptr][1];
this.w = anim[anim_ptr][2];
this.h = anim[anim_ptr++][3];
anim_ptr = anim_ptr % anim.length;
render(context, this);
}
};
(function(){
var ctx,
square = new Square(0,10,50,50);
canvas.width = 600;
canvas.height = 400;
ctx = canvas.getContext('2d');
(function loop(){
ctx.clearRect(0,0,600,400);
square.run(ctx);
setTimeout(loop, 1000/24); // We'll also set the FPS down a little
})();
})();
I know, it looks friggin weird. But it is an abstract animation nontheless. You could extend this to canvas transformation, importing images with sprites, and animating those, though that's beyond the scope of this tutorial.
So it's not as easy as Flash, but HTML5 is supposed to replace it eventually, so it's good to learn. Plus, I'm sure there are libraries and frameworks out there devoted to making complex animation in HTML5 an easy task.
Permanent Link to this page: http://bradenbest.com/tutorials/get_page.php?path=tuts%2Fhtml%2F&name=Animation+in+HTML5
Have an idea for a tutorial? Go to the Suggestion Box