Motion is an important aspect when building modern web applications. In fact, it’s important for all kinds of software products that involve user interfaces and interactions. Good user interfaces with well-designed animations help users understand the flow between two states. Imagine we are on a simple website that only has one button. We click that button and without any motion a box appears. Isn’t that boring? Also, we as a user might think that the box appeared because of our action. However, it could have been the result of something else like an http call in the background. In addition, animations can be used to make the user interface more snappy and responsive. They also explain changes in the arrangement of elements on the screen as some user actions may change the UI.
This could easily drift off into a whole new discussion about user experience and why motion matters. The bottom line is that motion not only makes a site more usable but also more fun. They tell stories, add a perceptible time dimension and improve the overall user experience of applications.
Let’s start with a deeper breath of how animations in the web generally work.
TABLE OF CONTENTS
Understanding state transitions
What’s a transition you may ask. Very good question! The Oxford Dictionary defines a transition as follows:
The process or a period of changing from one state or condition to another.
Applied to animations, a transition is the visualization of some state changing over time. A state could be a person sitting at the airport waiting for his plane to be boarded. It’s a condition something or someone is in at a specific point in time. A button on website could have 4 different states –
pressed, where the latter is a combination of
active. We could use a finite state machine or simply state transition system to visualize how it works:
The point is, a “system” or some element on the page can have multiple states. Instead of simply going from state A to B, we’d like to interpolate the values in between. Later in this post we’ll see how to use Angular’s animation system to implement a rich profile animation. For this it is necessary to understand the concept of states and state machines as it is kind of implemented as one. With
transitions we can listen for state changes and act accordingly.
What can we use to animate our UI?
As a matter of fact, we can take advantage of the same hardware acceleration in JavaSript too. It’s as easy as setting a CSS property with a 3D characteristic, such as
matrix3d(). This will push the particular element onto another layer which is then processed by the GPU. The GPU itself is highly optimized for moving pixels making it much for effective for animations compared to the CPU. For more information, check out this great article by Paul Lewis and Paul Irish on high performance animations.
CSS animations do not require any 3rd party libraries. However, there are some tools that make your life much easier, for example libraries that provide pre-defined keyframe animations like Animate.css.
.animate() and specify the properties (e.g. opacity or transform) we’d like to animate. Here is how we’d move a
div to the right by
200px animating its
While this works, it’s better to stick with either the
opacity property as those are the only things a browser can animate cheaply. Note that we use the string
slow to specify the duration of the animation. It is an equivalent for supplying a duration of
Let’s create the same animation as before, but this time using GSAP. More specifically we’ll use TweenLite, a lightweight animation tool that serves as the foundation of GSAP.
Note that the code above requires a plugin called CSSPlugin. This plugin allows us to animate almost any CSS property.
When working with frontend frameworks like Angular it could be a whole new story as some of them handle animations differently.
That doesn’t mean it’s not using some of the core concepts under the hood. They often come with their own animation system. Take Angular’s animation system for example, that is built on top of the Web Animations API (WAAPI). It’s fairly new and currently being implemented in Chrome and Firefox. It’s goal is to unite CSS, JS and SVG. More specifically, it aims to provide the power of CSS performance, add the benefits and flexibility of JS and SVG as well as leave the fundamental work to the browser without the need for additional dependencies.
If you want to learn more about the Web Animation API, check out this series of posts which goes a lot more into detail while covering advanced features like running multiple animations in parallel or in sequence, animating elements along a motion path or controlling animations using the
Here is a snippet showing the WAAPI in action:
Remember, the WAAPI is still a work in progress and things like additive animations are not fully supported yet. That’s why we use
getComputedStyle() to calculate the very first
KeyframeEffect is used to specify the values for the properties we’d like to animate. Each effect represents one keyframe and the values are basically interpolated over time. In other words, the array is a collection of keyframes. Here is an equivalent CSS keyframe animation:
Similar to the WAAPI, we also need to set the initial value when animating the
left property. This is not needed if we were translating the element on the X-axis to another location via its
transform property. With CSS keyframe animations we normally define when the change will happen with either percentage values or the keywords
to, which are the same as
We do this with the WAAPI by defining an
offset for each set of property values (keyframe). Keyframes without any offset will have offsets computed, e.g. the first keyframe has an offset of
0, the last will be
Case study: An animated modal-based user profile
Enough about theory! Let’s build a simple modal-based user profile and apply animations to improve the user experience and draw focused-attention to the dialog.
Here’s a preview of what we are going to build:
Our application is going to be very simple and mainly consists of two components:
DashboardComponent is the entry point (root component) of our application. It contains almost no logic and merely represents a wrapper composing the
DashboardComponent we initialize the data for the user profile and toggle the visibility of the dialog. An
ngIf will then show and hide the template. This is important for our animation because we use it as a trigger.
Here is the template of the
So far so good. Let’s look at the template of
To achieve the desired animation we need to set some initial CSS properties to enable 3D-space for the children elements inside
ProfileDetailsComponent. We do that by setting the
perspective on the
host element. CSS host selectors are a great way to apply styles without introducing an additional wrapper element.
Nonetheless, for our animation we still need a wrapper element because the
perspective property doesn’t affect how the host element is rendered; it simply enables 3D-space for children elements.
Again, the perspective only affects children elements and only those that are transformed in a three-dimensional space, e.g. rotation about X-axis or translation along Z-axis. The value for the perspective determines the strength of the 3D-effect. In other words, it describes the distance between the object and the viewer. Therefore, if the value is very small the effect will be quite impressive as we are extremely close to the object. On the other hand, if the value is high the distance between the object and the viewer will be large and therefore the animation looks rather subtle. That said, we need to set the
perspective property in order to achieve 3D-effects.
In addition, we have to specify a point of origin for our upcoming transformation. By default the point of origin is exactly the center of any element. The rest is just simple styling for the dialog.
Alright. Now that we got this in place, let’s implement our animation imperatively using GreenSocks’s timeline feature.
Imperative implementation using GreenSock
A timeline is basically a container where we place tweens over the course of time. Tweening is the process of generating intermediate frames between two states. With GSAP’s timeline we can easily build sequences of animations and animate an element
.from() a certain state. In addition, we get a lot of control over our animations. As such, we can stop, pause, resume, or even reverse them. Here is a simple example:
Check out the demo and try it out!
TimelineLite we have complete control over where tweens are placed on the timeline and they can overlap as much as we want. Notice how we use
.add() to add a label to the timeline. We can use labels to start multiple animations at the same time. For instance, we use this mechanism to run two animations in parallel. The
h1 will fade and translate in at the same time. Both animations could easily be combined in a single animation, but they have different easing functions. It solely demonstrates how to use labels.
Let’s see how we can do that in our Angular application. First off, we get all the elements using Angular’s built-in
@ViewChildren() decorators. We leverage those to query specific elements within the view of a component.
@ViewChild() returns an
@ViewChildren() returns a
QueryList. Essentially it’s an object that stores a list of elements and implements the
iterable interface. This makes it possible to be used in combination with
ngFor. The cool thing is that it’s based on Observables. This means we can subscribe to changes and get notified whenever an element is added, removed, or moved.
For more information check out Minko’s blog post about the difference between view children and content children in Angular.
Here’s how we use it in our Angular application to grab the elements we need:
The decorator takes either a type or a template reference variable. In most cases, such template reference variable is a reference to a DOM element inside a component’s template. The following snippet shows how we’d get a reference to the
#wrapper? That’s how we declare a local template reference for that specific element. We do this for all the elements we need for the animation. With that in place, we can instantiate our timeline.
Usually we use
ngOnInit to implement our initialization logic. However, this is a little bit too early in a component’s lifecycle because we have to wait for the component to be fully initialized in order to use the DOM elements we collected. There’s a lifecycle hook called
ngAfterViewInit which is the perfect moment in a component’s initialization process in which we have everything we need to set up the timeline.
Cool! But before we can construct the timeline for our profile animation there’s one thing left to do. We need to apply an initial transformation to the
wrapper element via CSS in order to achieve the fancy 3D-effect:
We can now apply the concepts we learned to build the timeline:
Woah! This looks pretty overwhelming at first glance. But all we have to do is to orchestrate our animation using GreenSock’s timeline API. With the help of labels we can run multiple animations in parallel and precisely control the timing of certain animations.
One thing we haven’t talked about so far is
.staggerFrom(). A stagger is an animation that contains a delay between each successive animation.
The whole animation can be illustrated as follows:
Here’s the full-fledge solution. Take a look and fiddle with it.
Declarative implementation using Angular Animations
In the previous section we have seen how to implement the profile animation with GreenSock in an imperative way. There are some drawbacks to that solution. First, it’s quite some boilerplate and work to collect all the elements and manually set up the timeline. That said, an animation platform like GSAP requires the DOM to be ready. A framework can make much more assumptions about the instructions (animation data) and the environment (app and browser) before animating things. Second, it can be very beneficial if there’s a framework backing the animation engine, like with Angular. GSAP and other animation libraries cannot easily handle DOM insertions or removals because they do not own the DOM transactions. Angular on the other hand has full control over the DOM.
If you are completely new to animations with Angular, check out this post by Thomas Burleson. It covers the fundamentals and shows an example of a more complex fade animation.
Alright, let’s refactor our profile animation using the latest animation features introduced with Angular 4.2. To get started, we first have to import the
@angular/platform-browser/animations and add it to the
imports of our application:
Remember, animations in Angular are based on the WAAPI and work in browsers that support it including Chrome and Firefox. However, currently Internet Explorer and Safari do not. In this case a polyfill is required to achieve similar results. Once animations are properly imported and enabled, we can go ahead and start defining the profile animation.
To quickly recap, animations are declared with the animations metadata property within the
@Component() decorator. Each animation is defined by a
trigger which takes a name and a list of
transition entries. Angular’s animation engine works basically like a state machine. That’s right. This should sound familiar. We have seen it in the beginning of this post, remember? The first argument of
transition allows you to specify a direction from one state to another, also known as state-change-expression. Here are some common values:
* => *captures a state change between any states
void => *captures the entering of elements
* => voidcaptures the leaving of elements
The last two are so common that they have their own aliases:
void => *
* => void
Angular 4.2 introduces several new animation features and extends Angular’s animation DSL. Here’s a quick overview of what’s new:
- query() can be used to find one or more elements within the element that’s being animated
- stagger() animates a bunch of elements with a delay in between each animation
- group() specifies a list of animations that are run in parallel
- sequence() specifies a list of animations that are run one at a time
- animation() can be used to create reusable animations with input parameters
- useAnimation() invokes reusable animations created with
- animateChild() will invoke child animations which are normally blocked
Neat! Let’s use that to re-implement our profile animation with Angular’s animation DSL. In order to demonstrate most of the above animation helpers, especially
animateChild(), we need to refactor our application a bit.
First of all, we create a new component called
ProfileStatsComponent which now contains the
ul that was previously part of the
DashboardComponent. The template of the
DashboardComponent now looks like this:
The dashboard now composes the
ProfileStatsComponent which will later define its own animation. For now, let’s focus on the profile animation and talk about child animations in a minute.
Here’s how we define our
profileAnimation we define one
transition and on
:enter (when the dialog enters the DOM) we run several animations in parallel. Next, we use
query() to grab the DOM elements we need for our animation and set some initial styles using the
Remember how we collected the DOM elements using
@ViewChildren()? We don’t need to do that anymore. Plus, we can get rid of all the local template references because that is now handled by
query(). Quite powerful, huh?
Before we implement the profile animation, let’s create a reusable fade animation that we can use elsewhere in different places with full input parameter support:
fadeAnimation can now be imported into our application, adjusted via input parameter and invoked using
useAnimation(). The values we specified for the input parameters are default values.
Once we have that in place, let’s add the missing pieces to our animation:
In the code above, we query a bunch of elements and use several animation helpers to achieve the desired effect. All animations will run in parallel because they are defined within a
group(). Also, there are no “labels” or a similar feature to what GreenSock provides with
.add(). Turns out, Angular has no timeline support yet and we need to fiddle with delays in order to orchestrate the animation.
If we take a closer look we can see that there’s actually more to it. For instance for the
wrapper, we run two animations in parallel one of which is the reusable animation we defined earlier. We can invoke a reusable animation with the
useAnimation() method. While
AnimationOptions are optional, we specify them to override the default input paramters.
Furthermore, we can spot this special style property of
style('*'). This will basically remove all of the special styling we have added (e.g. initial styles) and reset the state of the element. It’s an equivalent of setting each value to
*. This means that Angular will figure out the values at runtime. On top of that use the
stagger() animation helper method to animate multiple elements with a time gap in between each animated element.
Applying animations using @HostBinding()
Ok, but how do we use the animation? For that we can either attach the trigger to the element within the component’s template or use a
@HostBinding(). In our case, we use the
@HostBinding() because we want to attach the trigger to the host element:
Understanding child animations
In a real-world scenario you most likely end up having multiple components and animations on different levels, e.g. parent or child animations. Turns out that parent animations will always get priority and any child animation will be blocked. That’s a shame. But don’t bury your head in the sand yet because Angular got you covered! We can query inner elements and use
animateChild() to allow child animations to run. The cool thing is we can do that at any point in the animation sequence within a defined
In our example, we created a component called
ProfileStatsComponent. Let’s see this in action and start off by creating a child animation for this component using everything we know by now:
Easy, right? Now we can go ahead and use the
animateChild() helper as mentioned earlier in our
That’s it. We fully re-implemented the profile animation using Angular’s built-in animation system. It’s very intuitive, easy to use and declarative.
Here’s the demo. Try it out and fiddle with it!
If you want to find out more about the new animation features in Angular 4.2+, check out this excellent post by Matias Niemela.
Kudos to Matias Niemelä for the amazing work on the Angular Animation system!