Web Animation using JavaScript: Animating Text
Julian Shapiro is a world class developer. I first met him when he dropped CSS vs. JS Animation: Which is Faster?, which caught industry attention, and then he was nice enough to return with The Simple Intro to SVG Animation. It didn't take long to realize that Julian was a special talent.
Julian has recently written an exceptional book: Web Animation using JavaScript: Develop & Design. Julian has blessed us all with the fifth chapter of his book: Animating Text. Enjoy!
Since textual animation is rarely employed in webpages, using it is an easy way to impress users. That's precisely what makes this topic so much fun to learn: the underlying techniques are simple to program, but the results feel incredibly rich and complex to the user.
This article introduces you to tools that remove the tedious aspects of textual animation and equip you with an efficient workflow. Read on to learn the nuances of this dark art.
The standard approach to text animation
The standard HTML elements we code sites with—div
s, table
s, anchor tags, and the like—are the lowest-level components of a webpage that can be styled. Consequently, these are the lowest-level components that can be animated.
Text does not constitute an element unto itself; a block of text is designated by the browser as a text node, which is an unstylable, lower-level component that must be contained by an element. Further complicating matters is the fact that the browser does not subdivide text nodes into grammatical components; there is no way to access individual letters, words, or sentences.
Consequently, to animate text on a letter, word, or sentence basis, you have to break each text node into separate text nodes, and then wrap each of these in a new element. You can then animate them. But manually wrapping text in span elements, for example, is tedious work that results in bloated HTML.
It's no surprise then that text animation on the web is uncommon; it's typically too much of a hassle to deal with. This puts the web at an aesthetic disadvantage to dedicated motion design software, such as Adobe After Effects, which allows for the fine-grained animation of text—the results of which are commonly seen in TV commercials and movie title sequences. These effects can look absolutely beautiful. Unfortunately, in addition to being difficult to integrate on the web, they're also widely considered bad practice. After all, the web is a medium that prioritizes function over form, and text animation is largely about form.
However, there is one textual animation use case that can carry over well to the web when used sparingly: if you pay close attention to the depictions of futuristic hardware interfaces in movies, you'll notice the common thread of text being animated into or out of view on a grammatical level. The future of computing, according to pop culture, consists of words and sentences animating with flickers, glitches, pops, and blurs. These effects look cool, and there isn't much downside to embracing them for the purposes of transitioning content into or out of view since the text had to undergo a visibility animation by one means or another. This concept of transitioning text visibility is precisely what you'll learn about in this article.
Preparing text elements for animation with Blast.js
The tool of choice for typographic animation is Blast.js, which handily breaks blocks of text into characters, words, and sentences. You can then animate the resulting parts using Velocity and its UI pack plugin.
NOTE: Get Blast.js at Julian.com/research/blast.
Blast.js has three delimiter types to define the grammatical components to be individually extracted: character, word, and sentence. Suppose you have a div
that looks like this:
<div> Hello World </div>
If you call Blast on this div
using the following syntax:
$("div").blast({ delimiter: "word" });
the div
would turn into this:
<div class="blast-root"> <span class="blast">Hello</span> <span class="blast">World</span> </div>
As you can see, Blast separated the target div
's text into text parts that are individually wrapped in span elements. If you were to instead use the character
delimiter, the result would have been:
<div class="blast-root"> <span class="blast">H</span> <span class="blast">e</span> <span class="blast">l</span> <span class="blast">l</span> <span class="blast">o</span> <span class="blast"> </span> <span class="blast">W</span> <span class="blast">o</span> <span class="blast">r</span> <span class="blast">l</span> <span class="blast">d</span> </div>
You can now animate these span elements independently. Before you dive into textual animation, however, you're going to learn more about how Blast works so you can take full advantage of its powerful features.
How Blast.js works
The goal of this section is to make you comfortable with the prospect of using Blast to break apart the text on your beloved page. Let's dive in!
divs
, tables
, and the other HTML elements that you're familiar with are called element nodes. An element node commonly consists of two types of children: additional element nodes and text nodes (raw text).
Take this element, for example:
<div> Hello <span>World</span> </div>
This div
element is composed of two children: a text node ("Hello") and a span element node. The span element node contains a child of its own: another text node ("World").
When Blast is called, it traverses the entirety of the target element's descendant element chain to find text nodes. With each text node, Blast executes the RegEx query associated with the specified delimiter type (character
, word
, or sentence
) to subdivide the node into new elements, each with its own text node part. Since Blast doesn't actually subdivide element nodes—only text nodes—you can safely apply it to the entire page without worrying about breaking elements' event handlers and other expected behaviors. This versatility is crucial when using Blast on user-generated content that is often dirtied with HTML. (Say, for example, you want to separate the words in a message posted to your site's comments section so you can highlight important passages. With Blast, you can safely do so without concern for breaking the user's embedded links.)
In addition to its robustness, Blast provides a high level of accuracy. It doesn't dumbly split words at spaces, nor does it split sentences at periods within words. It leverages UTF-8 character sets for Latin alphabet languages, meaning that you can accurately apply it to French, German, Spanish, English, Italian, and Portuguese content.
Suppose you used Blast's sentence
delimiter on the following paragraph. (Note that bold and italic are used below to indicate the consecutive text matches that Blast detects.) Blast correctly identified six sentences in the paragraph:
¿Will the sentence delimiter recognize this full sentence containing Spanish punctuation? ¡Yes! « Mais, oui ! » "Nested "quotes" don't break the sentence delimiter!" Further, periods inside words (e.g. Blast.js), in formal titles (e.g. Mrs. Bluth, Dr. Fünke), and in "e.g." and "i.e." do not falsely match as sentence-final punctuation. Darn. That's pretty impressive.
Notice how punctuation is associated with its proper sentence, and how errant periods don't falsely demarcate sentence matches.
With these foundations covered, it's time to run through how to use Blast.
Installation
Blast is installed on a page like any other JavaScript plugin: embed the appropriate script link before your page's </body>
tag:
<html> <head>My Page</head> <body> My content. <script src="jquery.js"></script> <script src="velocity.js"></script> <script src="blast.js"></script> </body> </html>
NOTE: Blast requires jQuery (or Zepto, a jQuery alternative), and therefore must be required after jQuery. It doesn't matter whether Blast is loaded before or after Velocity.
Once Blast is loaded, use it by calling .blast() on a jQuery element object. It accepts an options object as its sole argument:
$element.blast({ option1: value1, option2: value 2 });
Let's run through the available options.
Option: Delimiter
Blast's most important option is delimiter
, which accepts "character"
, "word"
, or "sentence"
. To separate the text within $element using the "sentence" delimiter, your code would look like this:
$element.blast({ delimiter: "sentence" });
Note that Blast returns the generated text wrapper elements to the jQuery selector chain so you can manipulate them, like this:
$element.blast({ delimiter: "sentence" }) .css("opacity", 0.5);
The .css() call is applied to the individual text elements, not the parent $element that you called Blast on.
Option: customClass
Blast provides two options to make text manipulation easier: customClass and generateValueClass
. customClass
behaves exactly as you would expect: supply a custom class (as a string value) to be assigned to the text node wrapper elements.
Suppose you had the following div
and Blast call:
<div> Hi Mom </div>
$("div").blast({ delimiter: "word" , customClass: "myClass" });
The div
would turn into the following (note how Blast automatically assigns every text part the "blast" class by default):
<div> <span class="blast myClass">Hi</span> <span class="blast myClass">Mom</span> </div>
The value in providing a custom class is in differentiating the elements generated by each Blast call. If, for example, you used Blast in two locations on your page—once in the header and once in the footer—it might be helpful to assign these two calls different classes so your subsequent JavaScript code and CSS styles can act on the text elements appropriately.
Option: generateValueClass
generateValueClass takes a Boolean value (true or false) indicating whether a unique class, in the form of .blast-[delimiter]-[textValue], should be assigned to the generated text elements.
NOTE: This option is applicable only to the character and word delimiters.
The [delimiter]
placeholder represents the delimiter type used in the call, and the [textValue]
placeholder represents the text contained within an individual element. Consider the following example:
$("div").blast({ delimiter: "word" , generateValueClass: true });
The element would turn into this:
<div class="blast-root"> <span class="blast blast-word-Hi">Hi</span> <span class="blast blast-word-Mom">Mom</span> </div>
When Blast is called with the letter
delimiter, the element would turn into this instead:
<div class="blast-root"> <span class="blast blast-letter-H">H</span> <span class="blast blast-letter-H">i</span> … and so on… </div>
The generateValueClass
option is useful when you need to use CSS or JavaScript to manipulate text matches based on the text contained with them. If, for example, you used this feature on a book excerpt, you could create a visualization of all instances of the word "and" by giving elements with the .blast.word-and class a yellow background:
// jQuery implementation $(".blast-word-and").css("background", "yellow"); // Raw JavaScript implementation document.querySelectorAll(".blast-word-and").forEach(function(item) { item.style.background = "yellow"; });
// CSS implementation .blast-word-and { background: yellow; }
Thanks to this feature, you can painlessly target text matches via either CSS or JavaScript without having to use messy custom code to individually check the text contents of each element.
Option: Tag
This option lets you specify the type of element that wraps text parts. The default value is span
, but you can pass in any element type (for example, a, div, p). Consider this example:
<div> Hi Mom </div>
// Use the "div" element as the wrapper tag $("div").blast({ delimiter: "word" , tag: "div" });
The element would consequently turn into this:
<div class="blast-root"> <div class="blast">Hi</div> <div class="blast">Mom</div> </div>
This feature is useful to ensure that the resulting text elements mimic the structure of the surrounding HTML. Perhaps nearby sibling elements are all of the div
type, in which case the above example may be appropriate.
You might also want to take advantage of the unique properties offered by different tag types. strong, for example, automatically bolds text, whereas div
forces each text match to begin on a new line thanks to div
's default display
value of "block"
.
Command: Reverse
You can undo Blast on an element by passing false
as the sole parameter into a Blast call. Hence, if your Blasted element looked like this:
<div class="blast-root"> <div class="blast">Hi</div> <div class="blast">Mom</div> </div>
and you passed in the following Blast call:
$("div").blast(false);
the element would return to its original structure:
<div> Hi Mom </div>
You might be wondering how this works: when Blast is reversed, it simply destroys the generated wrapper elements, then inserts raw text where the wrapper elements were previously. Note that this will break event handlers assigned to the new elements generated by Blast, but it won't break event handlers associated with the HTML that existed prior to Blast being initially called.
Reversing Blast in this way is a crucial component of textual animation since the modus operandi when animating elements on a webpage is to leave things as they were before you touched them. If, for example, you've Blasted apart a sentence in order to animate its words into view one at a time, you would subsequently reverse Blast upon completion of the animation. Consequently, JavaScript code that later interacts with the text won't have unexpected child elements that it has to parse out. In short, it's good practice to avoid leaving your HTML unnecessarily bloated so that further programmatic interaction with your elements doesn't become increasingly convoluted.
NOTE: To learn more about Blast, including its unique search capabilities and its compatibility with screen-reading software, visit its documentation at Julian.com/research/blast.
Now that you've separated your text elements, it's time to animate them.
Transitioning text into or out of view
The most common use of textual animation is animating text in and out of view. A basic implementation of this is to animate the words in a sentence into view one after another.
Replacing existing text
Let's start by creating a container div
with placeholder text that will be replaced by new text that animates into place:
<div> A message will load here shortly… </div>
Because the div
starts out as visible, Blasting the div
's text results in child text elements that are visible as well. Since your goal is to animate the generated text elements into view starting from a state of invisibility, you have to make the generated text elements invisible immediately after you call Blast:
$("div") .blast({ delimiter: "word" }) .css("opacity", 0); .velocity({ opacity: 1 });
This replaces the div
's existing text with a new message. Then it Blasts the div
using the word
delimiter. Since a call to Blast returns the generated text wrapper elements to the jQuery selector chain, you can easily extend the code to set the opacity of each text element to 0. This primes the elements for the subsequent Velocity call, which consists of a simple opacity animation.
You may have noticed that the above code results in all text parts animating into view simultaneously. This, of course, defeats the purpose of using Blast in the first place: if you wanted all of the div's content to animate into view simultaneously, you could have simply animated the div
itself. The goal here is actually to achieve a successive animation sequence that consists of one text element animating after another.
Staggering
This is where Velocity's UI pack comes into play. To impose a successive delay between animation start times within an element set, use Velocity UI pack's stagger
option, which expects a duration specified in milliseconds. Applying it to the previous code example, you get:
$("div") .html("This is our new message.") .blast({ delimiter: "word" }) .css("opacity", 0) .velocity("transition.fadeIn", { stagger: 50 });
The code above produces a successive delay of 50ms between the elements' animation start times. Importantly, note the Velocity call's previous { opacity: 1 }
argument for "transition.fadeIn"
, which is a premade fade effect included with Velocity's UI pack. Since the stagger
option works with UI pack effects, this example shows the effect that mirrors animating opacity
to a value only of 1
.
Be careful to keep stagger times to a low duration so that users aren't waiting needlessly while text fades into view. Keep in mind that the longer an element's word count, the greater the overall time an animation sequence will take to complete. Text element staggering is one of the easiest ways to slip into the bad practice of slowing down your interface.
Transitioning text out of view
The code example in the previous section only animated text into—not out of—view; the div
's preexisting text was immediately replaced by the new message. This doesn't necessarily make for poor motion design, but it is often beneficial from the perspective of motion design theory to unify animations such that an element fades out of view in a way that reflects the way it faded into view.
If you want the outward textual animation to mirror the inward animation, you could rework the code example as follows:
// Select the previously blasted text $("div .blast").velocity( // Animate the existing text out of view with the appropriate UI pack effect "transition.fadeOut", { // Stagger the outward animation as you did the inward animation stagger: 50, backwards: true, // When this outward animation is complete, begin the inward animation complete: function() { // Proceed with the inward animation $("div") .html(message) .blast({ delimiter: "word" }) .css("opacity", 0) .velocity({ opacity: 1 }, { stagger: 50 }); } } );
This begins by calling the Velocity UI pack transition.fadeOut
effect on the text parts generated by the div
having previously been Blasted. As with the inward direction, the stagger
option successively offsets the individual text part animations in the outward direction. New to this call is the use of Velocity UI pack's backwards
option, which pairs with stagger
to reverse the target element set's order so that the last element (the last word in the sentence) animates out of view before the second-to-last element does, and that element animates out of view before the third-to-last element does, and so on. When this outward animation sequence is complete, the inward animation is called from within the complete
callback.
Using the backwards
option for text animation provides two benefits. First, it helps mirror (create the inverse of) the inward animation, which consists of the first word animating into view before the second word does, and so on. Second, when the backward animation is immediately followed by the forward animation, the net result is an elegant chaining effect in which the last word in the backward direction and the first word in the forward direction occur back-to-back. This works to tie the two animation sequences together into what looks like one conjoined animation instead of two separate animations crudely glued together.
Transitioning individual text parts
Movie title sequences are well known for their inventive typographic motion design. The technique underlying many of these effects is singling out individual text elements for animation. That's what this section covers.
NOTE: For further inspiration for your UI's typographic animation, search YouTube for movie title sequences and take detailed notes! As long as you keep the principles of motion design in mind, you should feel encouraged to explore textual animation design in your interface.
To achieve fine-grained control over the elements that Blast generates, simply use CSS's nth-child
selector and jQuery's eq()
function. These functions behave similarly to one another, in that they allow for the selection of an element within a set based on that element's index. In other words, if you passed an integer value of 3 into these functions, they would target the third element (that is, third word) in the full element set (that is, multiword sentence):
// CSS implementation .blast:nth-child(3) { color: red; }
// jQuery implementation $(".blast").eq(2).css("color", "red");
Both examples above target the third element on the page that has the .blast class applied. (Note that jQuery's eq function is 0-based whereas CSS' nth-child is 1-based, hence the different integer values being passed into the examples.) Continue with a jQuery implementation to work toward a complete example:
<div> Current status: paused </div>
// Blast the div
using the word delimiter
$("div").blast({ delimiter: "word" })
// Select the third word in the sentence (the span containing the "paused" text)
.eq(2)
// Fade the third element out of view then replace its inner text with a new message
.velocity({ opacity: 0 }, function() { $(this).text("running"); })
// Fade the replaced text into view
.velocity({ opacity: 1 });
This Blasts a sentence, selects its third word ("paused"), fades the word out of view, replaces the faded word with a new word ("running"), then fades the new word into view. The net effect is that the status-indicating keyword within a sentence gracefully fades into a new word to alert the user of a change. This is a tremendously elegant effect that consists of only a few lines of simple code. If you were to perform this effect many times over a larger block of text, you could achieve an impressive effect in which one message appears to sporadically change into another.
Transitioning text fancifully
You could easily swap the transition.fadeIn effect you've used thus far with another effect from Velocity's UI pack. Some of the other effects are quite fanciful, ranging from transition.shrinkIn
, which causes an element to scale down into view, to transition.perspectiveDownIn
, which causes an element to rotate down into view like a hinged barn door.
NOTE: For a complete list of UI pack effects, including live demos, visit VelocityJS.org/#uiPack.)
Keep in mind that some effects use 3D transforms (rotateX, rotateY, and translateZ), which don't work with on elements whose CSS display value is set to "inline"—the default display
value for span and anchor elements in particular. The workaround is to set Blast's generated text elements to a display value of "inline-block"
, which keeps "inline"
elements behaving as they normally do while giving them the added functionality of "block"
elements (such as div
and p), in which position-related properties, including 3D transforms, can be styled. Taking this display
tweak into account, the inward text transition example would now look like this:
$("div") .html(message) .blast({ delimiter: "word" }) .css({ opacity: 0, display: "inline-block" }) .velocity("transition.perspectiveDownIn", { stagger: 50 });
This sets the Blasted text parts' display
values to "inline-block"
in the same call to jQuery's css()
function that sets the elements' opacity
to a starting value of 0
.
Textual flourishes
The final topic in this discussion of textual animation is the concept of flourishes, ambient animations that produce ongoing effects for aesthetic purposes. One example might be a string of text that flickers like a dying light bulb. Another might be having all the words in a sentence continuously animate to different shades of blue.
Both of these are bad ideas.
These effects distract users and ultimately amuse only you—the developer who enjoys toying with motion design. Never include animation just for the sake of animation; if a part of your page is meaninglessly drawing the user's attention away from the parts that have utility, go back to the drawing board.
The rare exception to this is status indicators—text such as "Loading…"—that keep the user abreast of what the interface is doing. These are appropriate targets for textual flourishes because the flourishes tell the user that the interface is still processing data (as opposed to having frozen). In this way, flourishes act as an engaging visual heartbeat.
So if textual flourishes are generally considered bad practice, why are we talking about this? Because flourishes that aren't animated are often a great idea! Consider this a non-animation bonus provided by Blast: you can stylize the text elements generated by Blast to produce colorful collages and other unique typographic designs. For example, you could break apart a website's slogan text ("Delivering happiness right to your door!") word-by-word to reduce the opacity of each successive word, thereby creating a subtle gradient effect that spans the entire sentence. Here's what that code would look like:
<div> Hi Mom </div>
// Blast the div then iterate through the generated text elements $("div").blast({ delimiter: "character" }).each(function(i, element) { // Successively reduce the opacity of each element with an arbitrary formula var adjustedOpacity = 1 - i/10; element.style.opacity = adjustedOpacity; });
Instead of iterating opacity
values, you could also iterate RGB values to create color-based gradients. For example, if you increased the blue component of text whose color initially starts as gray, you'd produce elements that are increasingly rich in blue as you go from first to last:
// Blast the div then iterate through the generated text elements $("div").blast({ delimiter: "character" }).each(function(i, element) { // Successively increase the blue color component of each element with an arbitrary formula var adjustedBlue = i*20; element.style.color = "rgb(0, 0," + adjustedBlue + ")"; });
Wrapping up
This is just the beginning of the possibilities created by granular text control. Other techniques include fine-tuning the coordinates of every letter in a word to produce a collage effect, or placing words around the circumference of a circle to mimic the typographic design you might find on a drink coaster.
While these techniques may be well-suited for bold, homepage centerpieces, they may not be appropriate for critical parts of your UI that are subject to repeated user interaction. Why? Because stylized text is harder to read at a glance than unstylized text. But if you consider the balance between form and function, you'll be fine!
This post has been a chapter taken from Julian's Web Animation using JavaScript book. Read the book to master the latest web animation principles--including animation performance, theory, workflow, and more.
About Julian Shapiro
Julian Shapiro is a startup founder and full-stack developer from Vancouver. Read more about him at Julian.com.
Recent Features
9 Mind-Blowing WebGL Demos
As much as developers now loathe Flash, we're still playing a bit of catch up to natively duplicate the animation capabilities that Adobe's old technology provided us. Of course we have canvas, an awesome technology, one which I highlighted 9 mind-blowing demos. Another technology available...
Vibration API
Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user. One of those simple APIs the Vibration API. The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...
Incredible Demos
CSS Rounded Corners
The ability to create rounded corners with CSS opens the possibility of subtle design improvements without the need to include images. CSS rounded corners thus save us time in creating images and requests to the server. Today, rounded corners with CSS are supported by all of...
Fading Links Using jQuery: dwFadingLinks
UPDATE: The jQuery website was down today which caused some issues with my example. I've made everything local and now the example works. Earlier this week, I posted a MooTools script that faded links to and from a color during the mouseover and mouseout events.
What happened to blast.js? Not working anymore and can’t find any relevant info about that.