Animated Headlines

Animated Headlines

A collection of animated headlines, with interchangeable words that replace one another through CSS transitions.

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the BSD-3-Clause license. You can support our project with a Paypal donation 🙌

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!

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.

effect preview

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);
   });
}

Join our newsletter

Get our monthly recap with the latest CodyHouse news