What we think, we become. Gautama Buddha

Recently I’ve been exposed to several examples of word fade-in effects on the web, reflecting the kind of visual effect you might associate with certain kinds of fantasy or suspense films. So far the solutions I’ve seen have all required a lot of markup and some heavy lifting with frameworks, but I realised that the effect could easily be recreated with JavaScript and the Web Animation API:

Mark

We have to start with some markup: that way (because we’re building the effect progressively), if the JavaScript fails to execute for any reason, the user will still be able to read the text. I’ll use a combined <blockquote>, <q> and <cite> tag to achieve this:

<blockquote>
    <q>What we think, we become.</q>
    <cite>Gautama Buddha</cite>
</blockquote>

Set

The basic typesetting is achieved with CSS:

blockquote {
  font-size: 3rem;
}
cite {
  display: block;
  text-align: right;
  font-family: Verdana, Arial, sans-serif;
  margin-top: 1rem;
  font-size: .9rem;
  color: #aaa;
  font-style: normal;
}
blockquote q {
  font-family: Georgia, serif;
  font-style: italic;
  letter-spacing: .1rem;
}

One nice aspect of the <q> element is that the quotes before and after the element can be customized with CSS. Rendering these as pseudo elements means that they are not counted as part of the original HTML string:

q {
  quotes: "“" "”" "‘" "’";
}
q:before {
    content: open-quote;
    margin-right: .8rem;
}
q:after {
  content: close-quote;
}
q:before, q:after {
  color: #ccc;
  font-size: 4rem;
}

The JavaScript we’ll add in a moment will surround each word with a <span> element. The following CSS will be applied to those elements only if the JavaScript works; if it doesn’t, the HTML will solely be affected by the CSS we’ve written so far.

blockquote q span { 
  will-change: opacity, filter;
  opacity: 0;
  filter: blur(0px);
}

Go

The script, added to the bottom of the page, is divided into three functions: a random number generator, a function that splits up the content of the quote into separate words and surrounds them with <span> tags, and the animation. I’ll start with the second function:

function splitWords() {
  let quote = document.querySelector("blockquote q"),
  quotewords = quote.innerText.split(" "),
  wordCount = quotewords.length;
  quote.innerHTML = "";
  for (let i=0; i < wordCount; i++) {
    quote.innerHTML += "<span>"+quotewords[i]+"</span>";
    if (i < quotewords.length - 1) {
      quote.innerHTML += " ";
    }
  }
  quotewords = document.querySelectorAll("blockquote q span");
  fadeWords(quotewords);
}

The one slightly unusual part of the script is the test of i against the number of words in the quote at the end; that’s so that each of the reconstituted words in the quote has a space after except for the last word.

The array of words is passed to the fadeWords function, which uses the following:

function getRandom(min, max) {
  return Math.random() * (max - min) + min;
}

The fadeWords function itself:

function fadeWords(quotewords) {
  Array.prototype.forEach.call(quotewords, function(word) {
    let animate = word.animate([{
      opacity: 0,
      filter: "blur("+getRandom(2,5)+"px)"
    }, {
      opacity: 1,
      filter: "blur(0px)"
    }], 
    { 
      duration: 1000,
      delay: getRandom(500,3300),
      fill: "forwards"
    } 
   )
  })
}

Each <span> element is brought from an opacity of 0 and a random blur to full opacity and no blur. The animation of these elements lasts one second, but each has a random delay, meaning that word starts its animation at a different time (or at least is likely to do so).

Everything is started with a call to splitWords at the end of the script:

splitWords();

Of course, this could be taken even further: you could fade in each word sequentially (by incrementally adding the delay on each word), or even preferentially (by adding <span> elements with different classes around certain words, and having the script pick up on that). I leave the rest to your imagination…

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/pRLMrE