I recently stumbled upon the Year in Music 2014 website. Among some fancy effects, the animated headline captured my attention. So I decided to put together a collection of CSS effects for headlines with rotating words!
👋 A new version of this component is available. Download now →.
Creating the structure
The HTML structure is the same for every animation. We used a <section>
element as container, just to give some margins and set a max-width. The headline is an <h1>
tag - block element. Each word is wrapped into a <b>
tag, all words inside the same <span>
element - inline element.
The class .rotate-1
(added to the <h1>
tag) is what determines the effect to apply. If you just want to use this nugget, this is how it works: you change the class, you change the effect. Piece of cake!
Some effects may require an additional class. For example: when each letter is animated separately, the class .letters
must be added to the <h1>
tag. Inside the index.html file you can check the names of the classes applied to every headline.
<section class="cd-intro">
<h1 class="cd-headline rotate-1">
<span>My favourite food is</span>
<span class="cd-words-wrapper">
<b class="is-visible">pizza</b>
<b>sushi</b>
<b>steak</b>
</span>
</h1>
</section> <!-- cd-intro -->
Adding style
Let's see together how to create the Rotate 1 effect. The process is similar for every animation, feel free to surf through the source files (or, why not, try to create the animation all by yourself!).
First thing first: let's apply the .rotate-1
class to the <h1>
tag!
The idea is to create a 3D rotation: the visible word should disappear by rotating over the X axis, the new word should appear from the bottom and take the free spot, always rotating over the X axis.
To work in a 3D space, we need to set a perspective value, otherwise animated elements will look flat. Remember: the Perspective property shouldn't be applied to the element that will be animated (through a CSS Transition, Transformation or Animation), but to a parent element. In this case I applied the perspective property to .cd-words-wrapper
:
.cd-headline.rotate-1 .cd-words-wrapper {
display: inline-block;
perspective: 300px;
}
With the Perspective property in place, we target the <b>
elements (each word) and we set opacity: 0;
and position: absolute;
. This way we hide all the words and we remove them from the flow of the document, as if they don't occupy a space anymore. Finally we use the .is-visible
class applied to the first <b>
element (first word) and we add opacity: 1;
and position: relative;
. This is how we make the first word visible.
Note that we are applying a transformation (rotateX(180deg)
) to each word to turn it over. transform-origin
value is set at the bottom (first value is X, second value is Y). It's an important detail: as you can see from the .gif above or the demo, the origin of the rotation is not the center and we need to specify it in CSS. Then of course the rotation is set back to 0 if the .is-visible
class is applied.
.cd-headline.rotate-1 b {
opacity: 0;
transform-origin: 50% 100%;
transform: rotateX(180deg);
display: inline-block;
position: absolute;
left: 0;
top: 0;
}
.cd-headline.rotate-1 b.is-visible {
position: relative;
opacity: 1;
transform: rotateX(0deg);
}
We need to trigger the animation: with the help of jQuery, we remove the class .is-visible
from the first element and add it to the second, then we remove it from the second and add it to the third etc - to create a loop. Every time we remove the .is-visible
class, we switch it with the .is-hidden
one. Why we need 2 classes: in each class we define a different CSS animation. The .is-visible
is to show a word, the .is-hidden
to hide it, of course. See how the CSS changes with the addition of the animations:
.cd-headline.rotate-1 b {
opacity: 0;
transform-origin: 50% 100%;
transform: rotateX(180deg);
display: inline-block;
position: absolute;
left: 0;
top: 0;
}
.cd-headline.rotate-1 b.is-visible {
position: relative;
opacity: 1;
transform: rotateX(0deg);
animation: cd-rotate-1-in 1.2s;
}
.cd-headline.rotate-1 b.is-hidden {
transform: rotateX(180deg);
animation: cd-rotate-1-out 1.2s;
}
All what remains to do is defining the keyframes for both animations. The reason why we chose Animations over Transitions is the extra power in defining intermediate events. This is how we create the "anticipation" effect. Working with keyframes is clearly working with motion concepts. You can step up your interactions by distributing them along a timeline.
@keyframes cd-rotate-1-in {
0% {
transform: rotateX(180deg);
opacity: 0;
}
35% {
transform: rotateX(120deg);
opacity: 0;
}
65% {
opacity: 0;
}
100% {
transform: rotateX(360deg);
opacity: 1;
}
}
@keyframes cd-rotate-1-out {
0% {
transform: rotateX(0deg);
opacity: 1;
}
35% {
transform: rotateX(-40deg);
opacity: 1;
}
65% {
opacity: 0;
}
100% {
transform: rotateX(180deg);
opacity: 0;
}
}
Events handling
To trigger the headline animation, we defined the animateHeadline()
function.
var animationDelay = 2500;
animateHeadline($('.cd-headline'));
function animateHeadline($headlines) {
$headlines.each(function(){
var headline = $(this);
//trigger animation
setTimeout(function(){ hideWord( headline.find('.is-visible') ) }, animationDelay);
//other checks here ...
});
}
This is used to trigger the hideWord()
function with a delay of 2.5s. This function removes the .is-visible
class from the first word and adds it to the second while removing the .is-hidden
class from the second and adding it to the first. The hideWord()
is then triggered again (with the same delay) in order to create the loop.
function hideWord($word) {
var nextWord = takeNext($word);
switchWord($word, nextWord);
setTimeout(function(){ hideWord(nextWord) }, animationDelay);
}
function takeNext($word) {
return (!$word.is(':last-child')) ? $word.next() : $word.parent().children().eq(0);
}
function switchWord($oldWord, $newWord) {
$oldWord.removeClass('is-visible').addClass('is-hidden');
$newWord.removeClass('is-hidden').addClass('is-visible');
}
One note: there are some effects which require to animate single letters separately (for example, the Type effect). For these animations, we added the .letters
class to the <h1>
and wrapped each letter inside an <i>
element using the singleLetters()
function.
singleLetters($('.cd-headline.letters').find('b'));
function singleLetters($words) {
$words.each(function(){
var word = $(this),
letters = word.text().split(''),
selected = word.hasClass('is-visible');
for (i in letters) {
letters[i] = (selected) ? '<i class="in">' + letters[i] + '</i>': '<i>' + letters[i] + '</i>';
}
var newLetters = letters.join('');
word.html(newLetters);
});
}