V for Vendetta: Kinetic Typography with HTML and CSS

Project Information

For this project I started playing around with webkit animations and their use for 'moving' text. I really like the kinetic typography videos on youtube, and wanted to experiment with that. But my interests are with the web technologies and not with video editing tools, so that's when I decided to see if I could do the same with HTML and CSS... And yes, at the end, I could do (almost) the same :)! Keep in mind, I did all this by hand every piece of code! I did not know that there was a script called lettering.js which would have saved me a lot of time ;). So if you want to do something like this, be sure to take a look at that script.


Click on this image to view the project, but be sure to use a webkit browser!

The HTML

Everything is made using 1 html file and 2 css files. One css file for the intro, where it says 'Click play to start' the other file is for all the moving words. As soon as you click play, the styleswitch is activated and the words start moving. Sometimes this takes a little too long, I haven't found a way to improve that. When that happens, reload your page and it should work. The sound is from the movie 'V for Vendetta' you can find the whole dialog here: http://www.imdb.com/...

The first thing I did was to set up the whole dialog with a code as readable as possible. I ended up creating definition lists, where every scene (don't know if that are really scenes, but that's how I called them) was a new listitem. I started the list with describing who was talking, this is hidden in the CSS. It's just to make the whole dialog more readable and has no other function. So for the first sentence "I can assure you, I mean you no harm" this became the html:

<!-- SCENE 1 -->
<dt>Hugo Weaving (V)</dt>
<dd id="scene1">
<p class="start">
   <strong>I</strong> 
</p>
<p class="act1">
   <em>can</em> 
<b>assure</b> 
<span>you,</span>
</p>
<p class="act2">
<strong>mean</strong> 
<em>you</em> 
<span>no</span>
</p>
<p class="act3">
<span>harm</span>
<b>.</b>
<strong>.</strong> 
<em>.</em> 
</p>
</dd> 

As you can see, each word has its own tag, with the use of lettering.js every word will be put in a span with a classname so you don't have to apply custom classes to every word yourself. It is very important that you can reference to each word seperately in your CSS, because we want them to appear after one another other and in different styles. Also, you can see each list as a new layer which are positioned absolute seen from the viewable area. This is needed to keep the text in one place (the "frame" of the video we're building).

The CSS

This is where the magic happens. As we take a look at our page at this moment, it is just a simple output of the sentence. It's now time to hide everything in the CSS and let it appear when the time is ready. I did this by hand as well, there is no script which calculates when the words needed to appear and disappear so I listened to the audio file over and over again and tried to match the time as close as possible to the appearing words in the CSS. But before we start adding effects to our words, we have to create our area where they will appear in. If you're watching a video on YouTube, the content appears in a frame and all videos have the same frame. So we have to create such a 'frame' as well. And as I mentioned before, the list-items should be placed absolute in this area. So this is what I started with, my viewable area is 800px wide and 600px high.

#main #stage{    
width: 800px;   
height: 600px;   
margin: 20px auto 0;   
display: block;    
overflow: hidden; 
position: relative;   
z-index: 100;
}

#stage dd {
position: absolute; /* Place all list-items absolute inside the stage */
}

Now that we have our area, it's finally time to start adding effects to our sentence and to make it look like a kinetic typography video. I messed up a little with the classnames, I used scenes as reference in the HTML file, but used stages in the CSS file. Best is to keep these the same cause in the end this became quite confusing for me. 

The most important css rules throughout this whole project are the animations. If you're totally new to that first read this great post by Chris Coyler over at ccs-tricks.com which tells you almost everything you need to know about css animations and how to use them. 

Our first sentence consits of 4 paragraphs and a total of 8 words and 3 dots: "I can assure you, I mean you no harm..." For every paragraph I created a new css rule and for every word in that paragraph I did the same. See below, the rules, without their final contents:

/*--------------------------SCENE1--------------------------*/
#stage #scene1{
/* All extra css for the frame */
}

#stage #scene1 .start,
#stage #scene1 .act1,
#stage #scene1 .act2,  
#stage #scene1 .act3{
/* css for the acts */
}

#stage #scene1 .start strong{
/* css for the word "I" */
}

#stage #scene1 .act1 em{
/* css for the word "can" */
}

#stage #scene1 .act1 b{
/* css for the word "assure" */


#stage #scene1 .act1 span{
/* css for the word "you" */


/* I think you now know what follows ;) */ 

Then I had to think about the animations I wanted to use for these words. I ended up with 3 animations: Fade in (@keyframes stage1fadein) , Fade out (@keyframes stage1fadeout) and Scale (@keyframes stage1scale). And this is where I messed up a little, I used stage1 but it's better to use scene1, cause that is what I was using in the HTML file. Note: In this example I only show the @keyframes, but to support all browser, don't forget to create the keyframes with prefixes as well. (@-moz-keyframes, @-o-keyframes, @-ms-keyframes , @-webkit-keyframes). So this is what my three animations looked like in the CSS:

/* Fade in animation) for 95% of the time the opacity is 0, the element isn't visible. In the last 5% the element fades from 0 to 1 and becomes visible. You can play with the values yourself */
@keyframes stage1fadein {   
0% {   
opacity: 0.0;   
}   
95%{   
opacity: 0.0;   
}   
100% {   
opacity: 1.0;   
}
}

/* Fade out animation) for 85% of the time the opacity is 1, the element is visible. In the last 15% the element fades from 1 to 0 and becomes invisible. You can play with the values yourself */
@keyframes stage1fadeout {   
0% {   
opacity: 1.0;   
}   
85%{   
opacity: 1.0;   
}   
100% {   
opacity: 0.0;   
}
}

/* Scale animation) for 49% of the time the opacity is 0 and the font-size is 100%, the element is invisible and has the same font-size as the whole paragraph. From 49-50% the element becomes visible still with a font-size of 100%. In the last 10% the element font-size increases to 300% and the margin-top changes to keep the element in the right position while growing. */
@keyframes stage1scalein {   
0% {   
font-size: 100%;   
margin-top: 0;   
opacity: 0.0;   
}   
49%{   
opacity: 0.0;   
}   
50%{   
opacity: 1.0;   
}   
90%{   
font-size: 100%;   
margin-top: 0;   
}   
100% {   
font-size: 300%;   
margin-top: -65px;   
}

Now we have the HTML and the animation ready, which means it is time to apply our animations to the HTML elements. Again let's take a look at the first sentence "I can assure you":

/* The letter I, which grows when it's pronounced for the second time */
#stage #scene1 .start strong{   
animation-name: stage1scalein;   
animation-duration: 1.3s;   
animation-timing-function: linear;   
font-size: 300%;   
margin-top: -65px;
}

As you can see in the code above I first assign the right animation to this element, then I tell the animation how long it takes to go from 0 - 100% and then I tell how the animation will proceed over time. You can put this together in one line, but to keep my code as readable as possible I assigned each value to a new line. After that I apply some normal css rules, it is very important to note that you need to place the end values of the animation here. In this example a font-size of 300% and a margin-top of -65px. This is because when the animation stops, it will jump to these values.

/* the words "can assure you" */
#stage #scene1 .act1 em{   
animation-name: stage1fadein;   
animation-duration: 0.8s;   
animation-timing-function: linear;
}

#stage #scene1 .act1 b{   
animation-name: stage1fadein;   
animation-duration: 0.9s;   
animation-timing-function: linear;
}

#stage #scene1 .act1 span{   
animation-name: stage1fadein;   
animation-duration: 1.0s;   
animation-timing-function: linear;
}

For these words I just need to tell them when they may appear. As you can see the only difference between them is the animation duration. Every word runs 0.1s longer than the previous word. Remember that for this animation I created a rule to make the word visible between 95-100%? This percentage is applied to the run time of the animation. Because each animation runs a little longer this means that each word fades in a little later.

And this is what you do for each of the words, every time again. There is one extra rule applied to the elements starting from the second scene. This rule delays the animation before it runs. This is needed to prevent all sentences appearing at once. You first need to finish scene 1 before you can start scene 2. So you can imagine, when we're at scene 22 the delay time is much higher. The delay time should be the sum of the runtime of all previous scenes.

animation-delay: 2.1s;

Basically this is how I did the whole project. I would suggest you take a look at both the source-code and css-file of the project, once you know how to read it it's very straightforward... I hope you enjoyed it, and if you have any questions, feel free to leave a comment...

The reactions

Within 12 hours after I published this project on forrst, I got a lot of positive reactions and even made it to the #1 position on popular posts at forrst. Here some reactions: