Design Method
Let’s Cook Pixel Pancakes! A CSS3 Animation Tutorial
It’s that time of week again! Cue the epic music; it’s Tutorial Tuesday!
As it’s Pancake Day, I thought it would be a good idea to use this for inspiration for today’s post. We will in fact be cooking some pancakes with our lovely pixelated friend, Bob.
Try hovering over him to take a look at the end result:
Doesn’t he look ecstatic? The animation above is achieved using a combination of HTML, CSS3 and images.
Before we proceed, I’d like to clarify that the purpose of this tutorial is to showcase what can be done via CSS3 animations. I won’t be creating JavaScript fallbacks; browsers that don’t support CSS3 animations will just see a static version of Bob (with the exception of the flame, which is animated by using a .gif file).
In addition, please note that I won’t be using vendor prefixes to keep things concise. Here’s an article on CSS Tricks in case you need help setting up vendor prefixes.
The HTML & CSS
Let’s start by preparing the images. Bob and his cooker are actually comprised of 6 layers stacked on top of each other as shown below:
The order in which they’re stacked on top of each other doesn’t generally matter, with the exception of the pancake and arm. These need to be placed behind the rest of the images by either defining z-index
values, or by utilising the natural stacking order; you’ll see the reason for this later. Personally I opted to use the natural stacking order as it’ll help me cut out a few lines of CSS. It’s also worth noting that I used an animated .gif for the flame.
Here’s the snippet of HTML I used:
[html]
<div id="animation">
<img id="pancake" alt="Pancake" src="images/pancake.gif" width="27" height="9" />
<img id="arm" alt="Bob’s arm" src="images/arm.gif" width="105" height="45" />
<img id="cooker" alt="Cooker" src="images/cooker.gif" width="93" height="177" />
<img id="fire" alt="Fire" src="images/fire.gif" width="21" height="21" />
<img id="bob" alt="Bob" src="images/bob.gif" width="78" height="231" />
<img id="head" alt="Bob’s head" src="images/head.gif" width="48" height="75" />
</div>
[/html]
The above just consists of 6 images placed within a container <div>
. Let’s add some CSS to make the images appear as they should.
[css]
#animation {
padding-top: 20px;
width: 205px;
height: 303px;
position: relative;
}
#animation img {
position: absolute;
top: 20px;
left: 0;
}
#animation #head {
left: 15px;
}
#animation #bob {
top: 92px;
}
#animation #arm {
top: 122px;
left: 63px;
}
#animation #cooker {
top: 146px;
left: 112px;
}
#animation #pancake {
top: 152px;
left: 135px;
}
#animation #fire {
top: 167px;
left: 136px;
}
[/css]
There’s 4 things I’ve done in the CSS above:
- I set the container to
position: relative;
so that I could position the child images in relation to the container, i.e.div#animation
. - I set the position of each image using
position: absolute;
. - I explicitly set the width and height of the container. This is required as the container would otherwise have no dimensions due to the fact that all it’s child elements are positioned absolutely within it.
- I added a little padding to the top of the container
<div>
, to give the head room to bob (no pun intended) up and down. In retrospect, I could have added some margin to the top instead, which would have slightly simplified the calculations of the image positioning. If you use the margin approach, make sureoverflow
is set tovisible
(which is default), else you’ll see some cropping.
We now have a static version of Bob to which we can add some CSS3 animation.
The Basics of CSS3 Animations
When it comes to creating CSS3 animations, we need to make use of the @keyframes
rule. As the name suggests, @keyframes
allow us to specify the key frames within the animation. The browser will then “tween” between the frames, i.e. it’ll automatically add the transition.
A simple example would be changing the background colour and font colour of a <div>
when a user mouses over it.
The above animation has 3 key frames:
- Initial frame – Dark blue background with white text.
- Intermediate frame – Light blue background with white text.
- Final frame – Light blue background with yellow text.
All we need to do is specify the above CSS styles within the @keyframes
rule, and use percentages to define when those key frames should occur within the duration of the animation. For example 0% would be the start of the animation, and 100% would be the end.
Here’s the @keyframes
rule for the animation above:
[css]
@keyframes example-animation{
0%{
backgound: #323a42; /* dark blue */
color: #fff; /* white */
}
50%{
background: #327bc4; /* light blue */
color: #fff; /* white */
}
100%{
background: #327bc4; /* light blue */
color: #ffe400; /* yellow */
}
}
[/css]
You’ll notice that I’ve put example-animation
after @keyframes
; this is the identifier for the animation and must be specified (obviously, you can name it what you want). It’s also worth noting that you must specify 0%
(or the keyword from
) and 100%
(or to
) as a bare minimum, else the keyframes declaration is invalid.
You can find out more about @keyframes over
at MDN.
I’m sure some of you may have already noticed, but there’s actually nothing in the code above to bind the animation to a specific element; all we’ve done is created a set of key frames. To attach it to an element we’ll need to use the animation
property, which has the following syntax:
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
So to attach it to our <div id="example-1">
upon hover over, we just add the following CSS:
[css]
#example-1:hover{
animation: example-1 2s; /* animate over 2 seconds */
}
[/css]
And we’re done!
Animating Bob’s Head
Now that we know how to go about creating CSS3 animations, let’s go ahead and animate Bob’s head. Here’s a timeline of what we plan to do:
So at 0%, 50% and 100%, the head is at the default state, and at 25% and 75% the head is translated and rotated (see CSS3 2D Transforms for more information).
Here’s the @keyframes
rules that’ll do exactly what I mentioned above:
[css]
@keyframes headBob {
0%,
50%,
100% {
transform: translate(0, 0) rotate(0);
}
25% {
transform: translate(-5px, -5px) rotate(-10deg);
}
75% {
transform: translate(5px, -5px) rotate(10deg);
}
}
[/css]
I think the code here is pretty self-explanatory, but a few things to note:
- Translate moves the element along the x-axis (first argument) and y-axis (second argument). A positive value will move the element right/down, whereas a negative value will move the element left/up.
- Rotate accepts one argument and will rotate in a clockwise fashion, unless a negative value is specified.
- When specifying multiple transform functions, they should be separated with a space as shown above.
We can then attach it to an element using animation
as previously mentioned. In this case we’re animating Bob when someone hovers over the canvas, so the following CSS will suffice:
[css]
#animation:hover #head {
animation: headBob 0.3s infinite;
}
[/css]
You’ll notice that I set the iteration-count
to infinite
, as I want Bob’s head to be bobbing the whole time the user’s mousing over the canvas.
Animating Bob’s Arm & Pancake
This is a little more tricky as we have to animate 2 image elements separately, while keeping them in sync. The timelines below should help clarify what we’re looking for. Please note that I’ve had to change the axis somewhat, so that the images fit!
Let’s run through that again step-by-step:
- 0% – Bob’s arm is in it’s default state, i.e. level. The pancake is hidden behind the pan.
- 10% – At this point, Bob’s arm has been rotated by 15° anti-clockwise. The pancake is still hidden behind the pan. We can achieve this by translating it along the y-axis.
- 20% – Bob’s arm is back to it’s starting position (and will remain there until the end of this iteration). The pancake on the other hand has been moved up further, as well as rotated 360° anti-clockwise.
- 50% – The pancake is moved back down behind the pancake, having undergone a 720° anti-clockwise rotation.
- 100% – No change, to mimic a delay between the flicks.
Now that we’ve outlined the exact steps in the animation, the CSS is relatively easy to code up:
[css]
/* Keyframes for the flick of the arm */
@keyframes flick {
0% {
transform: rotate(0);
}
10% {
transform: rotate(-15deg);
}
20%,
100% {
transform: rotate(0);
}
}
/* Keyframes for the pancake spin */
@keyframes spin {
0% {
transform: translate(0, 0) rotate(0);
}
10% {
transform: translate(0, -25px) rotate(0);
}
20% {
transform: translate(0, -65px) rotate(-360deg);
}
50%,
100% {
transform: translate(0, 0) rotate(-720deg);
}
}
/* Attaching the animation to the relevant elements */
#animation:hover #arm {
animation: flick 1s infinite;
}
#animation:hover #pancake {
animation: spin 1s infinite;
}
[/css]
So now we’ve got the head bobbing, the arm flicking and the pancake flying, surely we’re done?
Try hovering over Bob below to see what we’ve done so far:
Oh dear! Bob’s got himself an extendable arm!
The problem we’re seeing above is due to the transform-origin
property, which as it’s name suggests, defines the origin of the transform. This property defaults to the centre of the element in question and therefore Bob’s arm is rotating around the centre of the image of the arm.
What we actually need is for the transform-origin
to be his elbow, as that’s the pivot. Let’s go ahead and change that property:
[css]
#animation #arm {
top: 122px;
left: 63px;
transform-origin: 0 41px; /* The new transform origin, i.e. the location of his elbow */
}
[/css]
And there we have it! A fully-functional animation of Bob making pancakes!
Here’s a download link in case you’d like to download all the assets (HTML, CSS and images) used in the tutorial. I’ve also included the .less file with custom mixins, that I used to create the CSS. If you’d like to learn more about Less, my colleague Andy wrote an awesome Introduction to Less, which should help you get on your way!
Thanks for sticking around until the end of the tutorial. Hopefully you’ve learnt a thing or two about CSS3 animations. As always, if you have any questions or constructive criticisms, please don’t hesitate to leave a comment below!