Animation Workflow - Web Animation using JavaScript: Develop and Design (2015)

Web Animation using JavaScript: Develop and Design (2015)

Chapter 4. Animation Workflow

The animation code found on most sites is nothing short of a mess. If there’s one thing experienced motion designers miss about the old, ugly days of Flash, it’s a structured approach to motion design.

The contemporary approach to structuring animation code is twofold: leverage the workflow features of an animation engine (in this case, Velocity.js) to make your code more terse and expressive, and use code organization best practices so that it’s easy to modify your work later.

Say goodbye to deep-nesting JavaScript callbacks and to dirtying your stylesheets with unwieldy CSS animations. It’s time to up your web animation game.

CSS animation workflow

In an attempt to better manage UI animation workflow, developers sometimes switch from JavaScript to CSS. Unfortunately, once animations reach a medium level of complexity, CSS animations typically result in a significantly worse workflow.

Issues with CSS

While CSS transitions are convenient when used sparingly in a stylesheet, they’re unmanageable in complex animations sequences (for example, when all elements sequentially load into view upon page load).

CSS tries to address this issue with a keyframes feature, which lets you separate animation logic into discrete time ranges:

@keyframes myAnimation {
0% { opacity: 0; transform: scale(0, 0); }
25% { opacity: 1; transform: scale(1, 1); }
50% { transform: translate(100px, 0); }
100% { transform: translate(100px, 100px); }
}
#box { animation: myAnimation 2.75s; }

This specifies separate points within an animation’s timeline at which particular property values should be reached. It then assigns the animation to an element with an ID of #box, and specifies the duration of the keyframe sequence to complete within. Don’t worry if you don’t fully grasp the syntax above—you won’t be using it in this book. But before moving on, consider this: what happens when a client asks you to make the opacity animation one second longer, but keep the rest of the properties animating at their current durations? Fulfilling this request requires redoing the math so the percentage values properly align with a 1-second increase. Doing this isn’t trivial, and it certainly isn’t manageable at scale.

When CSS makes sense

It’s important to point out a situation in which you should be using CSS rather than JavaScript for UI animation: when you’re animating simple style changes triggered by a user hovering over an element. CSS transitions lend themselves beautifully to these types of micro-interactions, allowing you to accomplish the task in just a few lines of very maintainable code.

Working in CSS, you first define a transition on the target element so that changes in the specified CSS properties animate over a predetermined duration:

/* When this div's color property is modified, animate its change over a duration of 200ms */
div {
transition: color 200ms:
}

You then specify the value that each particular CSS property should change toward, per the transition rule. In the case of the hover example, the div’s text color will change to blue when the user hovers over it:

div:hover {
color: blue;
}

That’s it. In only a few lines of code, CSS handles interaction state for you: when the user hovers away from the div, CSS will animate the change from blue back to the preexisting text color over a duration of 200ms.


What Does Good Code Look Like?

Good code is expressive, meaning that its purpose is easy to grasp. This is crucial not only for coworkers attempting to integrate your foreign code, but also for yourself in the future, once you’ve forgotten your original approach. Good code is also terse, meaning that it accomplishes what it needs to in as few lines as possible; every line serves an important purpose, and it can’t be rewritten away. Lastly, good code is also maintainable, meaning that its individual parts can be updated without fear of compromising the integrity of the whole.


In contrast, coding this same effect in jQuery would entail the following:

$div
// Register a mouseover event on this div, which calls an animation function
.on("mouseover", function() {
$(this).animate({ color: "blue" }, 200);
})
// When the user hovers off the element, animate the text color back to black
.on("mouseout", function() {
// Note: We have to remember what the original property value was (black)
$(this).animate({ color: "black" }, 200);
});

This might not look so bad, but the code isn’t taking advantage of the fact that JavaScript provides an infinite amount of logical control. It goes out of its way to do something that CSS is designed for: triggering logicless animations that occur on the same element that the user is interacting with. Above, you’re doing in JavaScript what you could have done in fewer, more expressive, and more maintainable lines of CSS. Even worse, you’re not getting any additional feature benefits by implementing this functionality in JavaScript.

In short, if you can easily use CSS transitions to animate an element that’s never being animated by JavaScript (meaning there’s no potential for conflict), then you should code that animation in CSS. For all other UI animation tasks—multi-element and multistep sequences, interactive drag animations, and much more—JavaScript animation is the superior solution.

Let’s explore the fantastic workflow techniques JavaScript provides.

Code technique: Separate styling from logic

The first technique has profound workflow benefits, especially for teams.

Standard approach

In jQuery animation, it’s common to animate CSS classes onto elements using the UI add-on plugin (jQueryUI.com). When the module is loaded, jQuery’s addClass() and removeClass() functions are upgraded with animation support. For example, let’s say you have a CSS class defined in a stylesheet as follows:

.fadeInAndMove {
opacity: 1;
top: 50px;
}

You can then animate the CSS properties of that class (opacity and top in this case) onto the target element along with a specified duration:

// Animate the properties of the .fadeInAndMove class over a 1000ms duration
$element.addClass("fadeInAndMove", 1000);

The more common implementation of jQuery animation consists of inlining the desired animation properties within an $.animate() call, which uses the syntax demonstrated in Chapter 1, “Advantages of JavaScript Animation”:

$element.animate({ opacity: 1, top: 50 }, 1000);

Both implementations produce the same result. The difference is their separation of logic: The first implementation delegates the styling rules to a CSS stylesheet, where the rest of the page’s styling rules reside. The second mixes styling rules with the JavaScript logic responsible for triggering them.

The first approach is preferable due to the organizational cleanliness and flexibility gained by knowing where to look to make the appropriate style or logic changes to your code. CSS stylesheets exist for a reason; seasoned developers do not inline CSS into their HTML. That would conflate the purposes of HTML (structure) and CSS (styling), and make a site considerably more difficult to maintain.

The value of logic separation is further pronounced when working in a team environment, in which it’s common for developers and designers to bump heads while trying to edit the same file at the same time.

Optimized approach

With the review of standard methods out of the way, let’s look at the optimized approach. It’s just as beneficial—and often the best methodology for JavaScript-centric animation workflows—to shift animation styling logic into a dedicated JavaScript file (for example, a style.js) rather than a dedicated CSS stylesheet. Sounds weird, right? Perhaps, but it works brilliantly. This technique leverages plain old JavaScript objects to help you organize your animation code.

For example, your style.js file might look like this:

// This object is a parallel to the CSS class defined in the previous code example
var fadeIn = {
opacity: 1,
top: "50px"
};

In your script.js, which is the primary JavaScript file that controls animation logic, you would then have:

// Pass our named properties object into Velocity
$element.velocity(fadeIn, 1000);

To recap, in your style.js, you’ve defined a JavaScript object that’s populated with the CSS properties you want to animate. This is the same object that’s then passed into Velocity as a first argument. You’re not doing anything fancy here—just saving objects to named variables, then passing those variables into Velocity instead of the raw objects themselves.


Image Note

This technique works equally well with jQuery’s animate() function.


Image

The benefit of switching from CSS to JavaScript to segregate logic is that your style.js file is uniquely capable of defining animation options—not just animation properties. There are many ways to specify an option: one is to assign two member properties to a parent animation object to which you assign an expressive name. The first property on the object defines the animation’s properties; the second defines its options.

In this case, your style.js file would look like this:

var fadeIn = {
// p is for "properties"
p: {
opacity: 1,
top: "50px"
},
// o is for "options"
o: {
duration: 1000,
easing: "linear"
}
};

In the script.js file, you’d have:

// Pass in our clean and re-usable animation objects
$element.velocity(fadeIn.p, fadeIn.o);

Pretty and clean, right? Someone skimming it would understand its purpose, and would know where to look to modify its properties—the style.js file. Further, the purpose of this animation is immediately evident: because you’ve named the animation object appropriately, you know that the code serves to fade an object into view. You no longer have to mentally parse animation properties to assess the purpose of the animation.

This approach discourages you from arbitrarily setting options for each individual animation on a page since there’s now a bank of premade animation objects you can easily pull from. This results in leaner code and more consistent motion design. Consistency, as you learned in the previous chapter, is a key component of great UX.

But the best part is that this approach lends itself perfectly to organizing your animation variations together. For example, if you typically fade button elements into view with a duration of 1000ms, but you fade modal windows into view with a duration of 3000ms, you can simply split your options object into two appropriately named variations:

var fadeIn = {
p: {
opacity: 1,
top: "50px"
},
// Options object variation #1 uses a fast duration
oFast: {
duration: 1000,
easing: "linear"
},
// Variation #2 uses a slower duration
oSlow: {
duration: 3000,
easing: "linear"
}
};
// Animate using the fast duration.
$button.velocity(fadeIn.p, fadeIn.oFast);
/* Animate using the slow duration. */
$modal.velocity(fadeIn.p, fadeIn.oSlow);

Alternatively, you could nest “fast” and “slow” objects as children of a singular o options object. The choice of which implementation to use is based on your personal preference:

var fadeIn = {
p: {
opacity: 1,
top: "50px"
},
o: {
fast: {
duration: 1000,
easing: "linear"
},
slow: {
duration: 3000,
easing: "linear"
}
}
};
// Animate using the fast duration.
$button.velocity(fadeIn.p, fadeIn.o.fast);
/* Animate using the slow duration. */
$modal.velocity(fadeIn.p, fadeIn.o.slow);

If this seems like too much overhead, and if you have few enough lines of JavaScript to justify simply inlining all your animation logic, then don’t feel like a bad developer for skipping this approach altogether. You should always use whichever degree of abstraction best suits the scope of your project. The takeaway here is simply that animation workflow best practices do exist if you find yourself needing them.

Code technique: Organize sequenced animations

Velocity has a small add-on plugin called the UI pack (get it at VelocityJS.org/#uiPack). It enhances Velocity with features that greatly improve the UI animation workflow. Many of the techniques in this chapter, including the one discussed below, make use of it.

To install the UI pack, simply include a <script> tag for it after Velocity and before the ending </body> tag of your page:

<script src="velocity.js"></script>
<script src="velocity.ui.js"></script>

The specific UI pack feature discussed in this section is called sequence running. It will forever change your animation workflow. It is the solution to messily nested animation code.

Standard approach

Without the UI pack, the standard approach to consecutively animating separate elements is as follows:

// Animate element1 followed by element2 followed by element3
$element1.velocity({ translateX: 100, opacity: 1 }, 1000, function() {
$element2.velocity({ translateX: 200, opacity: 1 }, 1000, function() {
$element3.velocity({ translateX: 300, opacity: 1 }, 1000);
});
});

Don’t let this simple example fool you: in real-world production code, animation sequences include many more properties, many more options, and many more levels of nesting than are demonstrated here. Code like this most commonly appears in loading sequences (when a page or a subsection first loads in) that consist of multiple elements animating into place.

Note that the code shown above is different from chaining multiple animations onto the same element, which is hassle-free and doesn’t require nesting:

// Chain multiple animations onto the same element
$element1
.velocity({ translateX: 100 })
.velocity({ translateY: 100 })
.velocity({ translateZ: 100 });

So what’s wrong with first code sample (the one with different elements)? Here are the main issues:

Image The code bloats horizontally very quickly with each level of nesting, making it increasingly difficult to modify the code within your IDE.

Image You can’t easily rearrange the order of calls in the overall sequence (doing so requires very delicate copying and pasting).

Image You can’t easily indicate that certain calls should run parallel to one another. Let’s say that halfway through the overall sequence you want two images to slide into view from different origin points. When coding this in, it wouldn’t be obvious how to nest animations that occur after this parallel mini-sequence such that the overall sequence doesn’t become even more difficult to maintain than it already is.

Optimized approach

Before you learn about the beautiful solution to this ugly problem, it’s important to understand two simple features of Velocity. First, know that Velocity accepts multiple argument syntaxes: the most common, when Velocity is invoked on a jQuery element object (like all the code examples shown so far), consists of a properties object followed by an options object:

// The argument syntax used thus far
$element.velocity({ opacity: 1, top: "50px" }, { duration: 1000, easing: "linear" });

An alternative syntax pairs with Velocity’s utility function, which is the fancy name given to animating elements using the base Velocity object instead of chaining off of a jQuery element object. Here’s what animating off the base Velocity object looks like:

// Velocity registers itself on jQuery's $ object, which you leverage here
$.Velocity({ e: $element, p: { opacity: 1, scale: 1 }, o: { duration: 1000, easing: "linear" } });

As shown above, this alternative syntax consists of passing Velocity a single object that contains member objects that map to each of the standard Velocity arguments (elements, properties, and options). For the sake of brevity, the member object names are truncated to the first letter of their associated objects (e for elements, p for properties, and o for options).

Further, note that you’re now passing the target element in as an argument to Velocity since you’re no longer invoking Velocity directly on the element. The net effect is exactly the same as the syntax you used earlier.

As you can see, the new syntax isn’t much bulkier, but it’s equally—if not more—expressive. Armed with this new syntax, you’re ready to learn how the UI pack’s sequence-running feature works: you simply create an array of Velocity calls, with each call defined using the single-object syntax just demonstrated. You then pass the entire array into a special Velocity function that fires the sequence’s calls successively. When one Velocity call is completed, the next runs—even if the individual calls are targeting different elements:

// Create the array of Velocity calls
var loadingSequence = [
{ e: $element1, p: { translateX: 100, opacity: 1 }, o: { duration: 1000 } },
{ e: $element2, p: { translateX: 200, opacity: 1 }, o: { duration: 1000 } },
{ e: $element3, p: { translateX: 300, opacity: 1 }, o: { duration: 1000 } }
];
// Pass the array into $.Velocity.RunSequence to kick off the sequence
$.Velocity.RunSequence(loadingSequence);

The benefits here are clear:

Image You can easily reorder animations in the overall sequence without fear of breaking nested code.

Image You can quickly eyeball the difference between properties and options objects across the calls.

Image Your code is highly legible and expressive to others.

If you combine this technique with the previous technique (turning CSS classes into JavaScript objects), your animation code starts to look remarkably elegant:

$.Velocity.RunSequence([
{ e: $element1, p: { translateX: 100, opacity: 1 }, o: slideIn.o },
{ e: $element2, p: { translateX: 200, opacity: 1 }, o: slideIn.o },
{ e: $element3, p: { translateX: 300, opacity: 1 }, o: slideIn.o }
]);

Expressiveness and maintainability aren’t the only benefits to sequence running: you also gain the ability to run individual calls in parallel using a special sequenceQueue option which, when set to false, forces the associated call to run parallel to the call that came before it. This lets you have multiple elements animate into view simultaneously, giving a single Velocity sequence the power to intricately control timing that would normally have to be orchestrated through messy callback nesting. Refer to the inlined comments below for details:

$.Velocity.RunSequence([
{ elements: $element1, properties: { translateX: 100 }, options: { duration: 1000 } },
// The following call will start at the same time as the first call since it uses the `sequenceQueue: false` option
{ elements: $element2, properties: { translateX: 200 }, options: { duration: 1000, sequenceQueue: false },
// As normal, the call below will run once the second call has completed
{ elements: $element3, properties: { translateX: 300 }, options: { duration: 1000 }
];

Code technique: Package your effects

One of the most common uses of motion design is fading content in and out of view. This type of animation often consists of a series of individual animation calls that are chained together to deliver a nuanced, multistage effect.

Standard approach

Instead of simply animating the opacity of an element toward 1, you might simultaneously animate its scale property so that the element appears to both fade in and grow into place. Once the element is fully in view, you might choose to animate its border thickness to 1rem as a finishing touch. If this animation were to happen multiple times across a page, and on many different elements, it would make sense to avoid code repetition by turning it into a standalone function. Otherwise, you’d have to repeat this non-expressive code throughout your script.js:

$element
.velocity({ opacity: 1, scale: 1 }, { duration: 500, easing: "ease-in-out" })
.velocity({ borderWidth: "1rem" }, { delay: 200, easing: "spring", duration: 400 });

Unlike the sequencing technique discussed in the previous section, the code above consists of multiple animations that all occur on the same element. Chained animations on a singular element constitute an effect. If you were to improve this effect by implementing the first technique in this chapter (turning CSS classes into JavaScript objects), you’d have to go out of your way to uniquely name each argument object for each stage in the overall animation. Not only is it possible that these objects wouldn’t be used by other portions of the animation code due to the uniqueness of this particular sequence, but you’d have to deal with appending integers to each animation call’s respective objects to delineate them from one another. This could get messy, and could neutralize the organizational benefit and brevity of turning CSS classes into JavaScript objects.

Another problem with effects such as the one above is that the code isn’t very self-descriptive—its purpose isn’t immediately clear. Why are there two animation calls instead of one? What is the reasoning behind the choice of properties and options for each of these individual calls? The answers to these questions are irrelevant to the code that triggers the animation, and should consequently be tucked away.

Optimized approach

Velocity’s UI pack lets you register effects that you can subsequently reuse across a site. Once an effect is registered, you can call it by passing its name into Velocity as its first parameter:

// Assume we registered our effect under the name "growIn"
$element.velocity("growIn");

That’s a lot more expressive, isn’t it? You quickly understand the code’s purpose: An element will grow into view. The code remains terse and maintainable.

What’s more, a registered effect behaves identically to a standard Velocity call; you can pass in an options object as normal and chain other Velocity calls onto it:

$element
// Scroll the element into view
.velocity("scroll")
// Then trigger the "growIn" effect on it, with the following settings
.velocity("growIn", { duration: 1000, delay: 200 })

If the UI pack is loaded onto your page, an effect such as this is registered using the following syntax:

$.Velocity.RegisterEffect(name, {
// Default duration value if one isn't passed into the call
defaultDuration: duration,
// The following Velocity calls occur one after another, with each taking up
a predefined percentage of the effect's total duration
calls: [
[ propertiesObject, durationPercentage, optionsObject ] ,
[ propertiesObject, durationPercentage, optionsObject ]
],
reset: resetPropertiesObject
});

Let’s break down this template step by step:

1. The first argument is the name of the effect. If the effect is responsible for bringing an element into view (as in, it fades an element’s opacity from 0 to 1), it’s important to suffix the effect with “In”.

2. The second argument is an object that defines the effect’s behavior. The first property in this object is defaultDuration, which lets you specify the duration the full effect should take if one is not passed into the Velocity call that triggers the effect.

3. The next property in the object is the calls array, which consists of the Velocity calls that constitute the effect (in the order that they should occur). Each of these array items is an array itself, which consists of the call’s properties object, followed by the optional percentage of the total duration which that call should consume (a decimal value that defaults to 1.00), followed by an optional options object for that specific call. Note that Velocity calls specified within the calls array accept only the easing and delay options.

4. Finally, you have the option of passing in a reset object. The reset object is specified using the same syntax as a standard Velocity properties map object, but it is used to enact an immediate value change upon completion of the full effect. This is useful when you’re animating theopacity and scale properties of an element down to 0 (out of view), but want to return the element’s scale property to 1 after the element is hidden so that subsequent effects needn’t worry about the properties beyond opacity they must reset on the element for their calls to properly take effect. In other words, you can leverage the reset properties map to make effects self-contained, such that they leave no clean up duties on the target elements.

In addition to the reset object, another powerful workflow bonus of the UI pack’s effect registration is automatic display property toggling. When an element begins animating into view, you want to ensure its display value is set to a value other than “none” so the element is visible throughout the course of its animation. (Remember, display: none removes an element from the page’s flow.) Conversely, when fading an element out, you often want to ensure its display value is switched to "none" once its opacity hits 0. This way, you remove all traces of the element when you’re done using it.

Using jQuery, display toggling is accomplished by chaining the show() and hide() helper functions onto animations (oftentimes messily buried within nested callbacks). With Velocity’s UI pack, however, this logic is taken care of automatically when you suffix your effect names with “In” and “Out” as appropriate.

Let’s register two UI pack effects—one for the “In” direction and one for the “Out” direction—and call the element “shadowIn” since it consists of fading and scaling an element into view, then expanding its boxShadow property outward:

$.Velocity
.RegisterEffect("shadowIn", {
defaultDuration: 1000,
calls: [
[ { opacity: 1, scale: 1 }, 0.4 ] ,
[ { boxShadowBlur: 50 }, 0.6 ]
]
})
.RegisterEffect("shadowOut", {
defaultDuration: 800,
calls: [
// We reverse the order to mirror the "In" direction
[ { boxShadowBlur: 50 }, 0.2 ],
[ { opacity: 0, scale: 0 }, 0.8 ]
]
});

If your effect’s name ends with “Out”, Velocity will automatically set the element’s display property to “none” once the animation is complete. Conversely, if your effect’s name ends with “In”, Velocity will automatically set the element’s display property to the default value associated with the element’s tag type (for example, "inline" for anchors, "block" for div and p). If your effect’s name does not contain one of these special suffixes, the UI pack will not perform automatic display setting.

Registering effects not only improves your code, but also makes it highly portable between projects and among fellow developers. When you’ve designed an effect you love, now it’s painless to share the effect’s registration code with others so they can use it too. Pretty neat!

Design techniques

The techniques discussed so far in this chapter will improve your workflow during the coding phase of motion design. The techniques covered in this section focus on the design phase, where you’re still experimenting to find the perfect animation that fits your UI. This phase requires a lot of creativity and a lot of repetition, and is accordingly ripe for workflow improvements.

Timing multipliers

The first design technique is to use a global timing multiplier. This consists of sprinkling in a multiplier constant against all of your animations’ delay and duration values.

Start by defining your global timing multiplier (arbitrarily designated as M for multiplier):

var M = 1;

Then, bake the multiplier into the duration and delay option values within each animation call:

$element1.animate({ opacity: 1 }, { duration: 1000 * M });
$element2.velocity({ opacity: 1 }, { delay: 250 * M });


Image Note

if you use SASS or LESS, which provide support for variable usage within stylesheets, this technique applies equally to CSS animations!


Embedding a multiplier constant will help you quickly modify the M constant in one location (presumably at the top of your style.js) in order to quickly speed up or slow down all of the animations across your page. Benefits of such timing control include:

Image Slowing down animations to perfect the timing of individual animation calls within a complex animation sequence. When you’re constantly refreshing your page in order to tweak a multi-element animation sequence to perfection, seeing the sequence in slow motion makes it significantly easier to assess how individual elements interact with one another.

Image Speeding up animations when you’re performing repetitive UI testing. When you’re testing a site for purposes other than animation, evaluating the end state of UI animations (how elements wind up) is more important than testing the animations’ motion. In these situations, it saves time and reduces headaches to speed up all the animations across your page so you’re not repeatedly waiting for your animations to play out on each page refresh.

Velocity has a handy implementation of this functionality called mock, which functions as a behind-the-scenes global multiplier so you don’t have to sprinkle in the M constant by hand. Like the example shown above, mock multiplies both the duration and the delay values. To turn mock on, temporarily set $.Velocity.mock to the multiplier value you want to use:

// Multiply all animation timing by 5
$.Velocity.mock = 5;
// All animations are now time-adjusted
// The duration below effectively becomes 5000ms
$element.velocity({ opacity: 1 }, { duration: 1000 });

Velocity’s mock feature also accepts a Boolean value: setting mock to true sets all durations and delays to 0ms, which forces all animations to complete within a single browser timing tick, which occurs every few milliseconds. This is a powerful shortcut for quickly turning off all animations when they’re getting in the way of your UI development and testing.

Use Velocity Motion Designer

Velocity Motion Designer (VMD) was crafted with the sole purpose of helping developers streamline the creation phase of motion design. VMD is a bookmarklet that you load onto a page in order to design animations in real time. It allows you to double-click elements to open a modal that lets you specify animation properties and options for that element. You then hit Enter on your keyboard to watch the animation play out immediately—without a page refresh.


Image Note

Get Velocity Motion Designer at http://velocityjs.org/#vmd.


Image

Once you’ve designed all your element animations exactly the way you want them, you can export your work into one-for-one Velocity code, which you can place immediately into an IDE for use in production. (The resulting code is also fully compatible with jQuery.)

Ultimately, VMD saves countless hours of development time by preventing constant IDE and browser tab switching and repeated UI state retriggering. Further, it streamlines the designer-to-developer workflow by allowing the two teams to work alongside one another in real time: with VMD, designers can implement motion design without having to familiarize themselves with a site’s JavaScript or CSS. They can simply hand off the exported Velocity code to the developers to integrate into the codebase at their discretion.

VMD is a highly visual tool—visit VelocityJS.org/#vmd to see the walkthrough video.

Wrapping up

As you implement animation workflow techniques, you’ll notice the intimidating black box of motion design beginning to unfold. The beautifully intricate loading sequences found on cutting-edge sites like Stripe.com and Webflow.com will start to make sense to you. You’ll gain confidence in your ability to code animation sequences, and this newfound skill will reduce friction in your development routine, making it not only easier but also significantly more fun to accomplish your motion design goals.