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

Web Animation using JavaScript: Develop and Design (2015)

Chapter 8. Animation Demo

It’s time to get your hands dirty! This final chapter walks you through a full animation demo powered by Velocity. In going through the demo’s source, you’ll learn how Velocity’s core features complement one another to greatly improve UI animation workflow. This demo will also introduce you to advanced techniques for working with CSS’s transform properties, which are vital to web-based animation today.

In short, you’re going to put together the skills you’ve accumulated throughout this book to make something really darn cool. Your goals in coding the demo are twofold: use terse and expressive animation code, and ensure maximum performance.

Behavior

The demo consists of 250 circles floating in, out, and around the screen. Periodically, you’ll zoom in, then back out to the position where the virtual camera started. The first image presented momentarily shows a zoomed-in view.


Image Note

Before you continue reading, head on over to VelocityJS.org/demo.book.html to see a live preview of the demo. (You can right-click anywhere on the page then choose “View Source” to see the demo’s code.)


The circle elements are simply normal divs with box-shadow and border-radius set in CSS. There’s no WebGL or Canvas animation going on here—just pure HTML element manipulation. (Given the volume of elements that are being animated at once, it’s quite impressive that this demo is capable of running so smoothly in the DOM!)

Let’s break down the animation: It consists of div elements translating along the X, Y, and Z axes. The Z-axis dictates the depth of each element’s animation, whereas X and Y provide the general flowing, 2D movement seen across the screen. Concurrent to the elements’ individual movements is a larger perspective shift that occurs on the element containing all these divs. This perspective shift occurs every 3 seconds, and it creates a periodic zooming effect that makes the viewer feel as if he’s briefly traveling through the circles’ 3D space.

The second graphic depicts the 3D scene in its zoomed-out view. Contrast this with the zoomed-in view shown in the first image.


How to Download the Code Sample

The code behind this animation demo is available for download from peachpit.com. Here’s how to get it:

1. Go to www.peachpit.com/register and create or log in to your account.

2. Enter the book’s ISBN (978-0-13-409670-4) and click Submit.

3. Your “My Registered Products” page opens. Find the listing for this book, and click “Access Bonus Content.”

4. The page containing the download
link opens—click to access the Animation Demo file entitled WebAnimationJS_DemoCodeSample.zip


Image

Code structure

Let’s take a look at the code that powers this demo. It is structured as follows:

5. Animation setup: The specification of parameters used for constraining animation movement.

6. Circle creation: The generation of the div elements to be animated.

7. Container animation: The code responsible for animating the circles’ parent element.

8. Circle animation: The code responsible for animating the circle elements themselves.

Try to familiarize yourself with the broader strokes of the demo’s implementation so you can keep its full context in mind as you explore each individual code block in the upcoming sections:

/************************
Animation setup
************************/
/* Randomly generate an integer between two numbers. */
function r (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/* Query the window's dimensions. */
var screenWidth = window.screen.availWidth,
screenHeight = window.screen.availHeight;
/* Define the z-axis animation range. */
var translateZMin = -725,
translateZMax = 600;

/**********************
Circle creation
**********************/
var circleCount = 250,
circlesHtml = "",
$circles = "";
for (var i = 0; i < circleCount; i++) {
circlesHtml += "<div class='circle'></div>";
}
$circle = $(circlesHtml);

/******************************
Container animation
******************************/
$container
.css("perspective-origin", screenWidth/2 + "px " + screenHeight/2 + "px")
.velocity(
{
perspective: [ 215, 50 ],
opacity: [ 0.90, 0.55 ]
}, {
duration: 800,
loop: 1,
delay: 3000
});

/***************************
Circle animation
***************************/
$circles
.appendTo($container)
.velocity({
opacity: [
function() { return Math.random() },
function() { return Math.random() + 0.1 }
],
translateX: [
function() { return "+=" + r(-screenWidth/2.5, screenWidth/2.5) },
function() { return r(0, screenWidth) }
],
translateY: [
function() { return "+=" + r(-screenHeight/2.75, screenHeight/2.75) },
function() { return r(0, screenHeight) }
],
translateZ: [
function() { return "+=" + r(translateZMin, translateZMax) },
function() { return r(translateZMin, translateZMax) }
]
}, { duration: 6000 })
.velocity("reverse", { easing: "easeOutQuad" })
.velocity({ opacity: 0 }, 2000);

Code section: Animation setup

This section’s code is copied below for easy reference:

/************************
Animation setup
************************/
/* Randomly generate an integer between two numbers. */
function r (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/* Query the window's dimensions. */
var screenWidth = window.screen.availWidth,
screenHeight = window.screen.availHeight;
/* Define the z-axis animation range. */
var translateZMin = -725,
translateZMax = 600;

The first section, Animation setup, begins by defining a function r (abbreviated from "random") that lets you artificially constrain randomly generated integer values. This function takes min and max parameters, then outputs a random number between the min to max range (using some basic algebra). You’ll use this later when randomizing the animating elements’ CSS transform values within ranges that are predefined in the next two code blocks.

The next section queries the window object to retrieve the monitor’s dimensions. By later referencing these values, you can ensure that the circles don’t animate too far off-screen (and consequently out of view).

Animation setup concludes by defining the min and max values for the elements’ Z-axis movement. These values control how small (far away) or large (nearby) you want the circles to animate from their initial size. Specifically, it dictates that the circles can go as far as 725 pixels along the Z-axis away from the virtual camera (away from the screen), and as close as 600 pixels toward the camera. In this case, there’s no constraint of going off-screen, but the circle could become too distant to see or so close that it takes up the entire monitor. Basically, it’s a creative decision.

Code section: Circle creation

/**********************
Circle creation
**********************/
var circleCount = 250,
circlesHtml = "",
$circles = "";
for (var i = 0; i < circleCount; i++) {
circlesHtml += "<div class='circle'></div>";
}
$circle = $(circlesHtml);

The demo’s second section, Circle creation, generates the primary div elements to be animated. Here, it first defines the desired number of circles as circleCount. Then, it defines a circlesHtml string to contain the circles’ collated HTML.

Next, it iterates up to the circleCount number to generate the circles’ HTML. Notice that it uses the performance best practice of batching DOM additions, as detailed in Chapter 7, “Animation Performance.” It collates the markup for each div element onto a master circlesHtmlstring that’s later inserted into the DOM in a single action. (If you were to insert the div elements into the DOM one at a time, the negative performance impact would be significant: UI interaction in the browser would be frozen until the relatively slow element insertion process completed.)

Finally, it wraps the circle elements in a jQuery element object so they can be easily manipulated as a group in the upcoming Circle animation section.

Code section: Container animation

/******************************
Container animation
******************************/
$container
.css("perspective-origin", screenWidth/2 + "px " + screenHeight/2 + "px")
.velocity(
{
perspective: [ 215, 50 ],
opacity: [ 0.90, 0.55 ]
}, {
duration: 800,
loop: 1,
delay: 3000
});

3D CSS primer

Let’s begin the first of the two animation sections of the codebase by focusing on the element that contains the circle elements. Before diving into the code, however, here’s a primer on how 3D animation works in the browser:

In order for 3D transforms to work (for example, translateZ, rotateX, rotateY), the CSS specification requires that the perspective CSS property be set on the element’s parent. In this case, that’s what the $container element is for.

The greater the value that perspective is set to, the less distance Z-axis translations (via CSS’s translateZ) appear to move relative to their origin. In other words, if you want more exaggerated depth in your 3D animations, set the parent element’s perspective property to something low, such as 50px, which is in fact the value that the container element is set to in the demo’s CSS. In contrast, a higher perspective value, such as 250px, would result in less visible movement from the origin point for every pixel that the element’s translateZ property is incremented by.

A separate and complementary CSS property is prospective-origin, which defines the angle at which the virtual camera is positioned. The virtual camera is the peephole through which the viewer sees 3D animation unfold in the browser. This section’s code block uses jQuery’s$.css() function to set a perspective-origin value on the container element that results in the camera being positioned at the center of the page, creating a perpendicular view of the 3D animation. This perpendicular view results in the appearance of circles flying directly toward and away from the viewer.

Specifically, this code section sets perspective-origin to the point on the page that’s at half the browser’s height and half its width—the center point of the page. This leverages the window dimensions queried in the Animation setup section.

With that context out of the way, let’s explore this section’s code.

Properties

This section’s code, reproduced below for easy reference, creates the demo’s zooming in and out effect:

$container
.css("perspective-origin", screenWidth/2 + "px " + screenHeight/2 + "px")
.velocity(
{
perspective: [ 215, 50 ],
opacity: [ 0.90, 0.55 ]
}, {
duration: 800,
loop: 1,
delay: 3250
});

While the prospective-origin property is set once on the container element and thereafter left alone, the prospective property is being animated by Velocity. This is necessary because the intended effect of the demo is to keep the vantage point into the scene stationary (perpendicular), but to exaggerate then de-exaggerate the distance of the elements from the virtual camera, which is where the perspective property’s animation comes in.

Specifically, this section uses Velocity to animate the perspective CSS property to a final value of 215px from a starting value of 50px.

By passing in an array as an animation property’s value, you’re forcefully specifying the final value to animate the property toward (215px, in the case above) as well as the initial value to animate from (50px, in the case above). While you certainly could have passed the property a single integer value as is typically expected by Velocity, the force-feeding syntax provides increased control over the property’s complete animation path.

You might be wondering, isn’t force-feeding unnecessary since Velocity knows how to retrieve a CSS property’s starting value on its own? While that is Velocity’s standard behavior when an integer is passed in as a value instead of an array, this isn’t always a desirable behavior due to the performance drawbacks inherent to querying the DOM for a property’s starting value. What the force-feeding syntax allows you to do is explicitly pass in a starting value so that Velocity can avoid querying the DOM for a property whose starting value you already know. In other words, the 50px starting value used in the perspective code above is the same value you initially set the container element’s perspective property to in the CSS stylesheet. You’re simply repeating the value here. Notice that this same force-feeding technique is used on the element’sopacity property as well: it’s animated to a final value of 0.90 from a starting value of 0.55 since that’s what the property was set to in the CSS.

As discussed thoroughly in Chapter 7, “Animation Performance,” DOM queries are indeed the Achilles’ heel of performant animation: the browser performs resource-intensive calculations to determine the visual state of an element. While it’s not important to the demo’s performance that you include this performance optimization, since the associated Velocity animation isn’t being triggered repeatedly inside a loop, it’s included nonetheless to contrast force-feeding’s secondary use, which you’ll learn about later in this chapter.

The net effect of animating the perspective and opacity is that all of the container’s circle elements appear to zoom in closer to the virtual camera while animating to an increased brightness (opacity goes from 0.55 to 0.90). The opacity boost mimics the way that light behaves in the real world: the closer the viewer is to the objects, the brighter they appear!

Options

The final section of Container animation code includes the options being passed into Velocity: duration, which is self explanatory; delay, which inserts a time-out at the start of the animation, and loop, which loops an animation back and forth between the values defined in the properties map and the element’s values prior to the animation occurring. Specifically, by setting loop to 2, you’re telling Velocity to animate to the values in properties map, back to where they were before, then to repeat this full loop iteration once more after a 3000ms delay.


Image Note

When delay is set alongside loop, the delay occurs between each of the loop’s alternations. Using delay creates a pleasant pause so that the zoom-in and zoom-out effect doesn’t zigzag back and forth abruptly.


Code section: Circle animation

This is where things start getting interesting. Let’s take a look the circle animation, in which you’re simultaneously animating their X-, Y-, Z-axis translations individually. You’re also animating their opacity.

/***************************
Circle animation
***************************/
$circles
.appendTo($container)
.velocity({
opacity: [
function() { return Math.random() },
function() { return Math.random() + 0.1 }
],
translateX: [
function() { return "+=" + r(-screenWidth/2.5, screenWidth/2.5) },
function() { return r(0, screenWidth) }
],
translateY: [
function() { return "+=" + r(-screenHeight/2.75, screenHeight/2.75) },
function() { return r(0, screenHeight) }
],
translateZ: [
function() { return "+=" + r(translateZMin, translateZMax) },
function() { return r(translateZMin, translateZMax) }
]
}, { duration: 6000 })
.velocity("reverse", { easing: "easeOutQuad" })
.velocity({ opacity: 0 }, 2000);

Value functions

Unlike the static animation property values used in the previous section (for example, [ 215, 50 ]), this section uses functions for property values: each property is force-fed an array of start and end values that are dynamically produced by functions. Let’s briefly explore these value functions, which are a unique feature of Velocity.


Image Note

Read more about value functions at VelocityJS.org/#valueFunctions.


Value functions let you pass in functions as animation property values. These functions trigger at run-time, and are called individually for each element animated in a set. In the demo, the set in question is the circle divs contained within the $circles jQuery element object. Consequently, each circle element property will be assigned its own randomized value once the animation begins. The only other way to achieve differentiation between animation properties in a set of elements is to animate the elements separately by looping through them, which gets messy and performs badly. This is the benefit of value functions—you keep dynamic animation code terse and maintainable.

Notice that, to produce the randomized values, this section of code leverages our r helper function that was defined in Animation setup. (As a reminder, the r function returns a randomized integer value constrained by its min and max arguments.) You’ll learn more about this function momentarily.

Opacity animation

The opacity property animates from and toward randomized values. In the case of the starting value, you’re giving the randomized value a slight boost to ensure that the elements are never too close to being invisible—after all, you want the viewer to see what you’re animating! The animation of opacity results in a smattering of circles all over the page that have varying opacities from the very first frame. Differentiated opacities create a nice gradient effect that adds visual richness to the demo.

This code takes advantage of force-feeding for a purpose other than performance optimization: value functions are being force-fed to dynamically generate start values for the elements that have yet to be inserted into the DOM, which means that you’re successfully avoiding writing an entirely new code block just to set the initial CSS states of the circle elements. You’re dynamically providing unique starting positions in the same line of code responsible for animating those positions. As discussed thoroughly in Chapter 4, “Animation Workflow,” you should strive for this level of expressiveness in all of your animation code.

Translation animation

For easy reference, here’s this section’s code once again:

/***************************
Circle animation
***************************/
$circles
.appendTo($container)
.velocity({
opacity: [
function() { return Math.random() },
function() { return Math.random() + 0.1 }
],
translateX: [
function() { return "+=" + r(-screenWidth/2.5, screenWidth/2.5) },
function() { return r(0, screenWidth) }
],
translateY: [
function() { return "+=" + r(-screenHeight/2.75, screenHeight/2.75) },
function() { return r(0, screenHeight) }
],
translateZ: [
function() { return "+=" + r(translateZMin, translateZMax) },
function() { return r(translateZMin, translateZMax) }
]
}, { duration: 6000 })
.velocity("reverse", { easing: "easeOutQuad" })
.velocity({ opacity: 0 }, 2000);

It’s time to examine the translate animations, which individually translate the circle elements’ positions within the demo’s 3D space. All three axes are animating from a randomized start value toward a randomized end value. The value operator, which consists of the plus sign followed by the equals sign (+=), tells the animation engine to animate properties incrementally from their starting values. In other words, the += value operator instructs the animation engine to treat the ending value as a relative value. In contrast, the default behavior of an animation engine is to interpret an end value in absolute terms.

As with opacity, this code section leverages force-feeding and value functions for their expressivity and performance benefits. In particular, the circles’ movement is constrained within ranges relative to the screen dimensions for the X and Y axes, and relative to the predefined min and max depth values for the Z-axis. (As a reminder, these values were set in the Animation setup section.) In the case of the X and Y axes, there’s an arbitrary fudge factor (notice the division by 2.75) to reduce how spread out the elements animate. This value is simply a creative decision; tweak it to suit your aesthetic preference.

Finally, the options object specifies that this entire animation should occur over a duration of 6000ms.

Reverse command

After the primary Velocity animation call, the chain continues with a call to Velocity’s reverse command. Reverse does exactly what it sounds like it: it animates the target elements back to their initial values prior to the previous Velocity call taking place. In this unique case, since start values have been force-fed into the previous Velocity call, those are the start values that reverse will animate back toward.

One of my reasons for including the reverse command in this demo is to extend the demo’s overall animation duration with a single line of maintainable and expressive code. (While you could double the duration of the animation from 6000ms to 12000ms, this would result in slowing down the movement of the circles.) The convenience of the reverse command is avoiding having to respecify—by hand—all of the animation start values. It would have been a huge mess to accomplish this manually since you would have had to first store all of the randomly generated start values into memory so you could animate back to them. Hence, reverse is yet another great Velocity feature that allows the demo to accomplish a lot with just a few lines of code.

Velocity’s reverse command defaults to the options object used in the previous Velocity call—including its duration, easing, and so on. In this case, since the previous call used a duration of 6000ms, so will the reverse call. The reverse command also lets you specify a new options object to extend onto the previous one. This demo uses a new easing type of easeOutQuad for added motion design flair in the animation’s reverse direction.


Image Tip

To preview the behavior of the popular easing types, visit http://easings.net.


When the reverse animation completes, a final Velocity call fades the elements out of view by transitioning their opacity values to 0 over a duration of 2000ms. This completes the demo by leaving the browser’s canvas in the same visual state it began in: clean and empty! Your work here is done.

Wrapping up

From force-feeding, to value functions, to reverse, this walkthrough has illustrated the power of the Velocity animation engine. Hopefully, this chapter has convinced you that this book’s focus on Velocity was worthwhile. In fewer than 75 lines of terse, legible, and performant code, you’ve created a rich 3D scene unlike anything you’ve seen before in pure HTML.

Let this demo serve as a concrete example of just how simple intricate-looking animations can actually be—especially when you use the right tools and employ best practices. My hope is that this book has distilled the beautiful animation work you’ve seen across the web into a set of tenets that are easy to grasp and follow in pursuit of your own motion design.

Now, go and design some beautiful websites and apps! Once you’ve put together something cool, show me on Twitter: twitter.com/shapiro.

Image