Animate On Triggering In Wrong Places Router

Animate On Triggering In Wrong Places Router

In this post, I'k going to cover the diverse animations use cases and the unlike implementations in Angular, using both Athwart specific animations and a combination of the more traditional methods and how to employ those within an Angular application.

This is a guide on the available animation options and which ones to use when. These includes some basics such as animations for country changes and entrances and exits, some more than advanced ones such as route transitions, sequences, keyframes, and queries, and alternatives such as class based, inline, and WAAPI animations.

I'll also provide some tips and tricks on organizing and optimizing your animation code, and how to use different browsers' devtools for debugging and analyzing your blitheness's functioning. Some things to proceed in heed to assistance keep your animations DRY, performant, and easier to debug.

Beneath are links to a live demo and the source lawmaking behind everything that is covered in this article:

  • Demo
  • Source code

High-level Overview

Angular animations (@athwart/animations) is a powerful module that comes with Athwart which provides a DSL (domain specific language) for defining web animation sequences for HTML elements equally multiple transformations over fourth dimension which could occur sequentially or in parallel. Angular animations use the native Web Animations API, and every bit of Angular half dozen, falls dorsum to CSS keyframes if the Web Animations API is non supported in the user's browser.

The animations are based on CSS spider web transition functionality which means that annihilation that tin can be styled or transformed through CSS, tin can be blithe the aforementioned way using Angular animations with the added advantage of giving the developer more than control in orchestrating information technology. This provides united states of america with animations that have CSS-like functioning along with the flexibility of Javascript out of the box without boosted dependencies.

Animations using Angular's BrowserAnimationModule goes through 4 steps. I like to think of this every bit being comprised of a series of questions - why, what, where, and how, the answers of which being what governs the animation's beliefs:

  • Evaluate information binding expression - tells Angular which animation land the host chemical element is assigned to (why)
  • Data bounden target tells Angular which animation target defines CSS styles for the elements country (what)
  • State tells Angular which CSS styles should be applied to the element (where)
  • Transition tells Athwart how it should apply the specified CSS styles when in that location is a land alter (how)

JS/CSS Convention

The style office is an integral part of Angular animation, a identify to specify what styles to apply to the target element at a sure state. An interesting matter about this function is that it accepts two types of conventions, which would explicate the varying syntax in animation code you would find on the cyberspace - some having camel case and some with dashed instance.

Camelcase

The Javascript naming convention is to use camelcase keys. Angular animation accepts this as is, letting you pass in regular key value pairs like this:

            

<> Copy

fashion ( { backgroundColor: "dark-green" , } )

Dashed Example

The CSS property naming convention (dashed case), still, has to be enclosed in quotes to stop Javascript from trying to interpret the hyphens as arithmetic operators. And then the same code in a higher place using the dashed case would await something like this:

            

<> Copy

style ( { "background-color" : "green" , } )

Gild of Execution

Angular animations happen after what triggers them. For case, the :enter state change will get chosen after ngOnInit and the beginning alter detection cycle whereas :leave would happen correct later on the chemical element ngOnDestroy is called.

In improver, each fourth dimension an blitheness is triggered, the parent blitheness will have priority over the children, blocking all child animations from executing unless explicitly stated to execute both. In lodge to run both animations, the parent animation must query each element containing the kid animations and run it using the animateChild method which is covered in more than item here.

Setup

In club to use @angular/animations in your application, yous will have to do the post-obit:

  • Verify that @athwart/animations bundle is installed and listed every bit a dependency in your package.json (it should be included past default)
  • If not, add together it by running npm install --relieve @angular/animations
  • Import BrowserAnimationsModule and add it to the module'south imports array (come across snippet below)
            

<> Re-create

import { NgModule } from '@athwart/core' ; import { BrowserAnimationsModule } from '@angular/platform-browser/animations' ; @ NgModule ( { imports: [ BrowserAnimationsModule ] , } )

Note: Angular also comes with NoopAnimationsModule which you tin can use to disable all animations globally.  It is more commonly used for testing to mock the real blitheness when the animations are either too irksome or doesn't play any role in what is beingness tested.

Basics

Beneath are a few bones utilize cases of the Angular animation module, more advanced use cases volition be covered in the subsequent section. Before diving into the utilize cases, let'south kickoff with getting a high level agreement of animation states which will be used in near sections of the postal service.

Blitheness States

Angular lets you define a style and transition to exist applied when an element's state changes. Angular provides 3 different states which we could use in our animation lawmaking:

  • Wildcard (*) - this is the default or all states of the element, for example active => * represents a country change from active to anything else.
  • Void (void) - this is the country when the element is created only non yet part of the DOM, or when the element is removed from the DOM.
  • Custom - this can be whatever custom proper name to indicate a certain state of the element (instance: 'active', 'inactive').

Transitions between State Changes

Before we start, nosotros will need to ascertain the different states for the element to transition between. This will be the beginning parameter that is passed in to the state function (which in the case below are 'default' and 'disabled'), along with the fashion that needs to exist applied when the element is in that country.

To animate the transition between the unlike state, we will need to pass in the transition function specifying the ii states (* => * in the instance below means annihilation to annihilation, we can apply a more than specific target besides such as default => disabled depending on your requirements) the transition needs to exist applied to, and the animation function that needs to be executed during the transition.

            

<> Re-create

import { trigger, state, style, breathing, transition } from '@athwart/animations' ; @ Component ( { ... animations: [ trigger ( 'enabledStateChange' , [ state ( 'default' , style ( { opacity: 1 , } ) ) , country ( 'disabled' , fashion ( { opacity: 0.v , } ) ) , transition ( '* => *' , breathing ( '300ms ease-out' ) ) , ] ) ] } )

Here is a brief caption of the methods used in the above snippet:

  • trigger - accepts a proper name for the blitheness trigger and an assortment of state and transition methods to configure the blitheness
  • land - accepts a proper noun for the state and the styles that should be conditionally applied when in specified country
  • fashion - CSS styles to exist applied
  • transition - specifies the configuration for transitioning between the different states and its direction
  • animate - specifies the duration and whatever additional CSS animation backdrop such as easing

manner, transition, and breathing accept both grouped (as an array) and singular arguments giving us some flexibility in terms of configuring our animations.

In your template, all you need to do is add the animation name defined previously, prefixed with @ and bind it to a variable that volition toggle between the states and Angular handles the residue.

            

<> Re-create

<div [@enabledStateChange] = "stateOfElement" > ... </div >
Content image Content image
Demo state alter animation

Enter And Get out Animations

Angular as well provides some useful aliases such every bit :enter and :leave to animate elements entering and leaving the DOM. These aliases are essentially transitions to and from the void state, i.e. void => * and * => void respectively. This is particularly useful for adding some blitheness to elements which are shown conditionally using *ngIf or *ngFor. The code below shows how you can create a fade in and fade out animation.

            

<> Copy

trigger ( 'fadeSlideInOut' , [ transition ( ':enter' , [ style ( { opacity: 0 , transform: 'translateY(10px)' } ) , animate ( '500ms' , style ( { opacity: 1 , transform: 'translateY(0)' } ) ) , ] ) , transition ( ':leave' , [ breathing ( '500ms' , style ( { opacity: 0 , transform: 'translateY(10px)' } ) ) , ] ) , ] ) ,

And to utilise it in your template, all yous need to do is add the trigger name prefixed by @. Since information technology is exclusively using the :enter and :get out aliases, we don't accept to bind it to anything.

            

<> Copy

<div *ngIf = "show" @fadeSlideInOut > ... </div >
Content image Content image
Demo enter exit animation

Advanced

As you tin can see in the basics section, a lot of the common use cases are fairly straightforward to implement. Hither I will go over some of the more advanced and perhaps less mutual use cases which could be useful for some scenarios.

Target Multiple Elements using Queries

The previous sections accept primarily focused on targeting single elements where the animation trigger is applied to. If nosotros accept an animation gear up that we want to apply to a group of elements all at one time with the same trigger, we tin can do so using the query part. An example of this would exist a list that applies the same animation to each list detail every bit it is added to the DOM.

A difference of using query compared to targeting a specific element is where the trigger is applied to. When using query, the blitheness trigger will be practical to the parent, where the query part will expect for elements that run across the query parameters within the parent (including nested children) which can and then be used to execute some animation. Out of the box, query accepts the following tokens:

  • :enter and:leave - returns all elements that are inserted or removed
  • :animating - returns all elements that are currently animating
  • :self - returns electric current element
  • @{animationName} - returns all elements with a matching blitheness trigger

Yous can as well query multiple of these properties together by passing in a comma separated string of the tokens to the query function. As I mentioned earlier, Angular lets you do with the queried elements essentially the same every bit when you target specific elements directly. The 2d parameter for the query function accepts either a single AnimationMetadata or an array of AnimationMetadata, which ways that it's possible to orchestrate complex animation sequences or logic inside a query office to target multiple elements.

Below is an example of how yous would apply a ShakeAnimation to all the children elements using the query function.

            

<> Copy

const ShakeAnimation = [ style ( { transform: 'rotate(0)' } ) , animate ( '0.1s' , style ( { transform: 'rotate(2deg)' } ) ) , animate ( '0.1s' , style ( { transform: 'rotate(-2deg)' } ) ) , animate ( '0.1s' , style ( { transform: 'rotate(2deg)' } ) ) , breathing ( '0.1s' , mode ( { transform: 'rotate(0)' } ) ) , ] ; consign const QueryShake = [ trigger ( 'queryShake' , [ transition ( '* => default' , [ query ( '.carte du jour' , ShakeAnimation ) ] ) , ] ) , ] ;
Content image Content image
Demo query multiple elements animation

Limiting the Number of Elements Queried

Piggybacking off the previous department, Angular'southward blitheness module also gives you the selection to limit the number of elements yous desire to animate from your query, with the power to practise a negative query: querying n number of items starting with the last chemical element.

This is specially useful if you want to add together some animations only to the first few or the final few of a set of dynamic elements (such as elements that are created with *ngFor). Using the previous instance's ShakeAnimation, we tin add an additional limit property to the query function passing in the number of elements to be returned.

            

<> Re-create

export const QueryShake = [ trigger ( 'queryShake' , [ transition ( '* => withLimit' , [ query ( '.menu' , ShakeAnimation , { limit: 2 , } ) , ] ) , ] ) , ] ;
Content image Content image
Demo query multiple elements with limit animation

Animating Children Elements

Angular animation comes with a handy office called animateChild() which as the name suggests, executes the kid's animation. Y'all might be request why would we need this if nosotros can execute the child'southward animation contained of the parent?

One of the mutual apply case for this is when you lot take an *ngIf attached to the parent and each of the children has its own animation triggers fastened to it with their own enter and leave animations. This is not a problem when the parent enters the DOM, all the children elements' blitheness will be executed normally as they are added to the DOM. Even so, the leave animation on the children elements doesn't actually work the same way. Considering the *ngIf is on the parent, once that boolean becomes false, the children volition immediately be removed from the DOM forth with the parent without executing their animation and waiting for it to be done before removing it. A fashion to handle that scenario is to attach a trigger to the parent and querying the children as part of the parent's blitheness. Beneath is an case of how we would employ the parent's animation to trigger its children'south animation.

Let's say nosotros have a simple container with 2 children, each with its own gear up of animations (different triggers) with the following construction.

            

<> Re-create

<div *ngIf ="isDisplayed" @container > <div @enterExitLeft > </div > <div @enterExitRight > </div > </div >
            

<> Copy

consign const EnterExitLeft = [ trigger ( 'enterExitLeft' , [ transition ( ':enter' , [ mode ( { opacity: 0 , transform: 'translateX(-200px)' } ) , animate ( '300ms ease-in' , fashion ( { opacity: 1 , transform: 'translateX(0)' } ) ) , ] ) , transition ( ':exit' , [ animate ( '300ms ease-in' , style ( { opacity: 0 , transform: 'translateX(-200px)' } ) ) , ] ) , ] ) , ] ; export const EnterExitRight = [ trigger ( 'enterExitRight' , [ transition ( ':enter' , [ style ( { opacity: 0 , transform: 'translateX(200px)' } ) , breathing ( '300ms ease-in' , way ( { opacity: i , transform: 'translateX(0)' } ) ) , ] ) , transition ( ':exit' , [ animate ( '300ms ease-in' , way ( { opacity: 0 , transform: 'translateX(200px)' } ) ) , ] ) , ] ) , ] ;

To be able to trigger all the children'due south blitheness using the parent container'due south *ngIf, nosotros will need to do a query with a wildcard to get all the children's triggers, followed by the animateChild() part to tell Angular to execute the blitheness that it finds on the queried elements.

            

<> Re-create

export const Container = [ trigger ( 'container' , [ transition ( ':enter, :leave' , [ query ( '@*' , animateChild ( ) ) , ] ) , ] ) , ] ;

What the code higher up does is it tells the parent to detect all the children of the element with an animation trigger (annihilation that starts with @) attached to it, and run the animation as function of the parent's animation sequence. In the code to a higher place, I used a wildcard prefixed with @, which returns all the children with an animation trigger. This might not be applicative to all use cases. For cases where you need to target specific children or maybe target dissimilar kid or child animations depending on a certain condition, nosotros can pass in a different target hither instead of @* as the query parameter depending on your needs.

Content image Content image
Demo children animation

Route Animations

Road animations refers to the animations that are applied to view transitions during a route change. Equally per the Angular docs, this is done by defining a nested animation sequence in the top-level component that hosts the view and the components that host the embedded views. This could also be applied to nested router-outlets in your application, the blitheness trigger just needs to be practical to the div that wraps the router-outlet.

To demonstrate this, we volition beginning demand to wrap the router-outlet inside a div which will contain the trigger for the blitheness. Then add an aspect directive in the router-outlet that contains data about active routes and their states which is and then used to assign an animation state value to the animation trigger based on the route configuration.

            

<> Re-create

<div [@routeAnimation] = "prepareRoute(outlet)" > <router-outlet #outlet = "outlet" > </router-outlet > </div >

Secondly, we demand to pass the outlet's electric current country to our routeAnimations using the router-outlet's activatedRoute belongings. This property will get updated every time a navigation occurs which in plow will trigger our animation. We will employ a helper role prepareRoute to do the necessary checks and return the value required by the routeAnimation trigger.

            

<> Copy

prepareRoute (outlet: RouterOutlet ) { return outlet?.isActivated || '' ; }

When an blitheness is triggered, we will have admission to the previous page through the :leave selector and the electric current folio through the :enter selector. Nosotros tin use these the same way as we would when we animate individual elements. With that said, you would also be able to apply the different sequences every bit described here to your animation. Below is an example of what a fade in and fade out route animation definition would look like.

            

<> Copy

const resetRoute = [ style ( { position: 'relative' } ) , query ( ':enter, :leave' , [ style ( { position: 'fixed' , // using accented makes the whorl get stuck in the previous page's scroll position on the new page peak: 0 , // accommodate this if y'all have a header and so it factors in the summit and not cause the router outlet to leap as it animates left: 0 , width: '100%' , opacity: 0 , } ) , ] , { optional: true } ) , ] ; // Fade Blitheness trigger ( 'routeFadeAnimation' , [ transition ( '* => *' , [ ...resetRoute, query ( ':enter' , [ style ( { opacity: 0 } ) ] , { optional: truthful , } ) , group ( [ query ( ':leave' , [ manner ( { opacity: ane } ) , breathing ( '0.2s' , fashion ( { opacity: 0 } ) ) ] , { optional: true } ) , query ( ':enter' , [ mode ( { opacity: 0 } ) , animate ( '0.5s' , style ( { opacity: 1 } ) ) ] , { optional: true } ) , ] ) , ] ) , ] ) ;

The elements in the query array are executed in the order they are in (looking at the code above, it will be executed from tiptop downwardly). The first resetRoute gets executed start which volition hide and prepare some properties to both the previous and current view to allow them to overlap. Both the views volition be nowadays in the DOM at the aforementioned time (the view that is being navigated to appears immediately instead of appearing after the view existence navigated from has disappeared) preventing them from stacking up and breaking the layout. This is followed by the bodily animations for the inbound view and the leaving view.

In that location is no difference in writing animation lawmaking for route animation and animation that targets regular html elements or Athwart components. Therefore, nosotros could use all of the blitheness properties that we would normally use on an element and employ to our route blitheness as nosotros meet fit.

Variable Route Animations

We can also pass in additional parameters through the router's data holding if we need variable animations. A common use case for this is if we want to trigger different enter and exit animations for unlike routes.

            

<> Copy

{ path: 'home' , component: HomeComponent , data: { animation: 'domicile' } , } , { path: 'post' , component: PostComponent , data: { animation: 'post' } , }

In order to get the additional parameter and apply it in our blitheness, we will have to alter the prepareRoute office to return the boosted parameter. Instead of returning the router's country, we will use the activatedRouteData property to access the data object and select the animation belongings.

            

<> Copy

prepareRoute (outlet: RouterOutlet ) { render ( outlet?.activatedRouteData && outlet. activatedRouteData [ 'animation' ] ) ; }

We tin so use the additional parameter in our animations assortment, treating them every bit different states that we can transition to and from like so:

            

<> Re-create

trigger ( 'routeAnimation' , [ transition ( 'home => post' , [ ] ) , transition ( 'post => domicile' , [ ] ) , ] ) ;
Content image Content image
Demo road animation

Disable Animations

Sometimes we desire to disable an animation when a sure status is met, for example, on low performing devices, sure browsers, when the user has the system setting fix to minimize the amount of non-essential motion (prefers-reduced-motion media query), or maybe an internal setting within the application. Athwart provides you with a @.disabled property that lets y'all do exactly this. This property lets you pass in an expression to conditionally disable and enable children animations, defaulting to true if no expression is passed in.

            

<> Copy

<div [@.disabled] = "disableAnimationCondition" > <div [@animate] = "expression" > Animate </div > </div >

This property disables all the animation on the element itself and all the children of the chemical element, including those that are rendered from within a router outlet. Nether the hood @.disabled adds/removes .ng-animate-disabled form on the chemical element where it's applied. This allows us to either disable animations on a specific component, sure sections of the application or even awarding wide.

Toggling the disabled state for the entire application can be done by calculation the disabled property through a HostBinding on the top level AppComponent like the snippet below. This will disable all the animations throughout the application, with a few exceptions that volition be covered in the post-obit department.

            

<> Copy

export class AppComponent { @ HostBinding ( '@.disabled' ) private disabled = truthful ; }
Content image Content image
Demo disable animation

Some gotchas of this holding

The disabled property only targets Athwart animations, hence animations that are implemented using CSS transitions or keyframe animations won't be disabled.

Some other caveat: it won't work for elements that are appended to the DOM directly. Some examples of these types of elements are overlays such every bit lesser sheets and modals. Instead of using the previous methods of adding the disabled property, we can use Angular's Renderer2 to set the attribute direct to the overlay containers to disable both the element and its children's animations.

            

<> Re-create

constructor ( private overlayContainer: OverlayContainer , private renderer: Renderer2 ) { const disableAnimations: boolean = true ; // become overlay container to fix holding that disables animations // Notation: how to become the container element might vary depending on what the element is const overlayContainerElement: HTMLElement = this . overlayContainer ; // angular animations renderer hooks up the logic to disable animations into setProperty this . renderer . setProperty ( overlayContainerElement, "@.disabled" , disableAnimations ) ; }

Elements that are added to the DOM directly could alternatively be disabled by importing the NoopAnimationsModule instead of the regular BrowserAnimationModule in the module that contains these components, which mocks the animations. However, this disables all the blitheness inside that module.

Recall of it like turning off your TV by flipping the lever on your circuit breaker. This might work for certain use cases such as when you want to disable all tertiary political party animations within a module, but this would probably not piece of work well for something that is more dynamic.

Animation Sequences

Animations tin run both in sequence and in parallel using the functions sequence() and group() respectively. A combination of parallel and sequence tin also be used where the animation runs with a cascading delay betwixt the elements. This effect is accomplished using stagger().

grouping and sequence are a footling dissimilar compared to stagger. The former are applied to blitheness steps (values within the animation assortment), whereas the latter are applied to the animated elements.

To demonstrate the various animation sequences, let's first with defining the template which contains the parent element that we will target in our animations along with a few children elements. This is commonly used in lists or grid-like components containing multiple same or like children elements. For simplicity, we volition animate the children elements entering the view, calculation a fade in and grow effect using the three sequences.

            

<> Copy

<div @fadeInGrow > <div > First Element </div > <div > Second Element </div > <div > 3rd Chemical element </div > </div >

Run Animations in Parallel

group lets y'all run multiple animation steps in parallel. An example of a apply case for this is if yous desire to animate multiple properties with varying animation properties such as unlike elapsing, delay or eases.

            

<> Copy

animations: [ trigger ( 'fadeInGrow' , [ transition ( ':enter' , [ query ( ':enter' , [ style ( { opacity: 0 , transform: 'scale(0.8)' } ) , grouping ( [ animate ( '500ms' , style ( { opacity: i } ) , animate ( '200ms ease-in' , style ( { transform: 'scale ( 1 )' } ) ] ) ] ) ] ) ] ) ]
Content image Content image
Demo grouping animation

Run Animations in Sequence

sequence works similar to grouping where it alters the animation steps execution. sequence runs the animation sequentially, executing animations in the animation array one after the other. This function simplifies the process of chaining animations for a single target element.

Comparing the code beneath and the code in the previous section, everything looks identical, except the group function, which is replaced with the sequence function. It works the exact same mode as running in parallel, the deviation being sequence volition tell Angular to execute the animation one after the other. Instead of fading in and growing the element at the aforementioned time, the transform animation will be executed afterwards the opacity animation is done.

            

<> Copy

animations: [ trigger ('fadeInGrow', [ transition (':enter', [ query (':enter', [ way ( { opacity: 0 , transform: 'calibration ( 0.8 )' } ) , sequence ( [ animate ('500ms', way ( { opacity: one } ) , animate ('200ms ease- in', style ( { transform: 'scale ( ane )' } ) ] ) ] ) ] ) ] ) ]
Content image Content image
Demo sequence animation

Stagger Animations

Dissimilar the previous 2 functions, stagger is applied to the blithe elements. This is usually used in conjunction with the query function to find inner elements within a parent/container element and applying animation to each of the kid individually. What makes stagger unique is that it takes in an boosted parameter timing to specify the delay for the blitheness'south execution for each chemical element creating a cascading consequence.

With stagger, the animation will be applied in the social club of the element queried. This usually results in a staggering effect from the acme down. Nosotros tin can easily reverse this order by passing in a negative value to the timing parameter resulting in the animation staggered starting from the last element and making its fashion up.

The second parameter in the stagger office accepts an assortment of fashion and breathing functions, which ways that we could as well utilize the previous sequences - sequence and grouping to time the private step of the animation together with stagger decision-making the timing of the individual elements.

            

<> Re-create

animations: [ trigger (fadeInGrow, [ transition (':enter', [ query (':enter', [ style ( { opacity: 0 } ) , stagger ('50ms', [ animate ('500ms', style ( { opacity: 1 } ) ] ) ] ) ] ) ] ) ]

Let's break down the new functions in the code above:

  • The trigger fadeInGrow targets the parent adding an :enter transition which will execute the blitheness in the transition array when the parent element enters the DOM.
  • query(':enter') inside the transition assortment targets all the children elements which will enter the DOM and applies the properties in the array that gets passed in which defines the elements' styles and animations.
  • stagger('50ms') in the assortment being passed in to the query role tells Angular to execute all the animations applied to the children elements with a fifty ms delay between each chemical element.
Content image Content image
Demo stagger animation

Multi-stride Animation using Angular Keyframes

Similar to how CSS keyframes animations work, keyframes allow us to build an blitheness in multiple steps. In other words, it lets us sequence our manner changes for each element. Since this method tin can exist passed in to the animate function, it tin can exist combined with the previous section'due south blitheness sequences - group, sequence, and stagger, giving usa fifty-fifty more control over the sequencing of our animations.

Angular's keyframe role comes with an offset property which accepts decimals ranging from 0 to ane to specify the steps of our animation. These are identical to the CSS keyframe counterparts of using percentages or to and from backdrop that nosotros unremarkably utilize to specify our animation steps. Below is an instance of a uncomplicated CSS keyframe blitheness, and what it looks similar when using Angular's keyframe function.

            

<> Copy

/* css */ @keyframes 'fadeSlideGrowKeyframe' { 30% { transform : opacity ( one )' } sixty% { transform : 'translateY ( 0 )' } 100% { transform : 'scale ( 1 )' } }
            

<> Copy

/* angular animations */ trigger ( 'fadeSlideGrowKeyframe' , [ transition ( ':enter' , [ style ( { opacity: 0 , transform: 'scale(0.v) translateY(50px)' } ) , animate ( '500ms' , keyframes ( [ style ( { opacity: ane , offset: 0.3 } ) , style ( { transform: 'translateY(0)' , offset: 0.6 } ) , fashion ( { transform: 'calibration(1)' , outset: 1 } ) , ] ) ) , ] ) ] )
Content image Content image
Demo multi-step keyframe animation

Implementation Tips

Reusing your animation

A lot of times some animations get reused in several places in the application which tend to lead to duplicated animation code in several components. We could abstruse our animation lawmaking in a few different means depending on the use example which I will show below to keep our animation lawmaking equally Dry out every bit possible.

Abstracting the entire animation trigger

This is probably the virtually straightforward way if there are no configurable pieces in your animation and you desire to go on the naming and behavior of the animation consistent beyond all your components. You tin abstract out your entire trigger into a separate file and utilize a combination of different triggers in the animations array in the component's decorator past passing in the imported animations.

            

<> Copy

// fade.animation.ts export const Fade = trigger ( 'fade' , [ transition ( ':enter' , [ style ( { opacity: 0 } ) , animate ( '500ms' , style ( { opacity: i } ) ) , ] ) , transition ( ':get out' , [ animate ( '500ms' , style ( { opacity: 0 } ) ) ] ) , ] ) ;
            

<> Copy

import { Fade } from './fade.blitheness' ; @ Component ( { animations: [ Fade ] , } )

Using the AnimationReferenceMetadata

This approach lets you laissez passer in boosted parameters to your blitheness making it configurable depending on the caller. A limitation to this is that it only works with pre compiled values. In other words, you lot won't be able to modify the parameters at run time, for instance with the element'south current position. If you need to be able to pass in run fourth dimension information, this is where I would recommend using AnimationBuilder and AnimationPlayer instead. In that location is a great article by GrandSchtroumpf which covers a workaround that lets yous utilize AnimationBuilder combined with AnimationReferenceMetadata to be able to use dynamic values (with some known limitations).

            

<> Copy

export const Slide = animation ( [ manner ( { transform: 'interpret({{x}}px, {{y}}px)' } ) , breathing ( '{{duration}}southward' , mode ( { transform: 'translate(0,0)' } ) ) , ] ) ; // utilize the animation from inside the trigger trigger ( 'slide' , [ transition ( ':enter' , useAnimation ( Slide , { params: { x: 0 , y: l , duration: 0.iii , } , } ) ) , ] ) ,

The major departure here compared to a regular animation is the apply of the useAnimation method in place of the array of animations, which accepts the animation nosotros created and a params object with whatever boosted parameters that the animation might await.

Disable Animations when Testing

If you aren't testing the animations itself, instead of using the BrowserAnimationModule which volition run your animations similar the real awarding (which might not exist useful for the unit tests and might even slow down the execution of your examination cases), you could import and use Athwart'southward NoopAnimationsModule instead. As the name suggests, noop (no-functioning) is a utility module which mocks the real animation simply doesn't actually animate it.

            

<> Re-create

@ NgModule ( { imports: [ // BrowserAnimationsModule // when running the main awarding NoopAnimationsModule // when running tests ] } )

Animation Performance

Maintaining a 60 fps frame rate when you lot are animative is very important as anything less will effect in a noticeable stutter or what is commonly referred to equally jank. The fundamental here is to exist able to place which properties are expensive to breathing and which aren't and utilizing the compositor thread wherever possible. We will go over some additional metrics that we should be aware of when writing our animation code in the next section.

UI take a specific cartoon sequence which are as follows (top being the first and bottom existence the final in the sequence):

  • Styles (margin, padding, etc.)
  • Layout (height, width, etc.)
  • Paint (groundwork, color, visibility, etc.)
  • Blended (opacity, transforms - rotate, scale, interpret, etc.)

The earlier you are in the sequence, the more expensive it is to animate since everything following information technology will take to exist executed. Layout changes are peculiarly expensive if you have a lot of elements on the page as it could potentially trigger a lot of recalculation to happen. For case, if yous have 10 elements on your folio, animating the width/meridian of the beginning element volition cause the 9 other elements to movement or change size to accommodate the new width of the kickoff chemical element. transforms and `opacity on the other hand are relatively inexpensive every bit those are on the blended stride of the drawing sequence.

Here is a comprehensive list of what each CSS property volition trigger that you lot can refer to as you are writing your animation.

CSS Animations and Web Animations both use the compositor thread which is independent of the main UI thread. That means, even if the main thread is doing some heavy task, your animation wouldn't be afflicted since it's on a different thread. Athwart Animations uses Web Animations APIs and CSS Animations, which means this is less of a concern. However, animation that requires paint or layout will still utilize the main thread. This, depending on how much work the main thread is going, could upshot in some stutter.

With that said, to have a silky smooth animation and avoid skipped/dropped frames, it is of import to optimize your animations. Brand sure you lot know the hidden implications and potential performance hitting of the properties you lot are animative. Wherever possible, avoid animating properties that would trigger layout or paint and try to stick to composite properties (opacity, rotate, translate, and calibration).

Performance Tooling

I really like how Liam DeBeasi breaks downward some of the key performance metrics that we should pay attention to when building our animations and how nosotros can employ tools in modern browsers to help u.s. visualize these metrics for our awarding during his talk at Ioniconf 2020. These metrics includes:

  • Boilerplate frames per 2d (FPS)
  • Main thread processing
  • Average CPU usage and free energy touch on

Nosotros can employ a couple of unlike tools to ensure that our animations are optimized and are being run efficiently. Let's dive in a little deeper on each of the points above, what we should be the goal metric we want to aim for, and how we can utilize the diverse tools to ensure that we are coming together these goals.

Average frames per 2nd

Nosotros desire this to be as close as 60 FPS as possible. Anything below sixty FPS will be noticeable to the users and will effect in stutter unremarkably known equally jank.

We can run across the actual frames per second during the animation's execution using Chrome devtools'southward functioning tab. You volition need to hitting the tape button and run your animation. Try to go out some fourth dimension earlier and after your animation when y'all tape to make sure that your timeline doesn't go cut prematurely before your blitheness has started/ended. If you lot click on the Interactions dropdown, you will come across an option for Animations. This will show y'all where in the timeline that animation happens.

You can use this data to identify which section in the timeline the animation is happening and either select that section on the timeline to view the average fps over that duration or hover over the Frames section of that menstruum in the timeline to get the bodily frames per 2nd data.

Content image Content image
Average FPS over the elapsing of the animation (Chrome devtools)
Content image Content image
FPS at a point in time during the blitheness (Chrome devtools)

I mode to make sure our animation code is hit the sixty FPS target is to brand sure that all the blitheness is being optimized past the browser and a proficient tool to utilise hither is Firefox's blitheness inspector. It shows a synchronized timeline giving united states of america a top downwards view of the animations. Below is what you would encounter on the animations tab of the inspector when a page has a running animation.

Content image Content image
Animations tab showing a timeline view (Firefox devtools)

Some things to note here are the colors of the charts on the main timeline view. The colors indicate dissimilar type of animations/transitions:

  • Green - spider web animations
  • Orange - CSS animations
  • Bluish - CSS transitions

Each chart can besides exist clicked on to get a more detailed view on what individual backdrop are being animated in that blitheness.

You lot volition too notice a greyness thunderbolt icon on the right of the chart on the main timeline view and a green thunderbolt on the script animation view below the main timeline view. This is to indicate if the animation being run is optimized by the browser or not. The animations or the individual backdrop that have a thunderbolt icon every bit shown in the image above means that the animation or property is optimized by the browser.

What we want to do based on this data is to make sure that all (if not, most) of our animations/properties are optimized and should bear witness up with the thunderbolt icon.

Main thread processing

The primary thread is responsible for a lot of things such equally layout and pigment (in terms of UI) and also evaluating javascript. We desire to go on this to a minimum to gratis up the main thread for our application to perform other tasks that do crave the main thread.

We tin utilize Chrome and Safari's Dev tools to test this metric, each tool giving us a different but equally useful insight to this metric.

Let's start with Chrome Devtools. Nosotros can use the performance tab similar to the previous section, only instead of focusing on the frames, we will highlight the department of the timeline that has the animation and select the main option from the sidebar. This will show the main thread usage over this flow. Every bit you can run across in the image beneath, Chrome devtools gives us the percentages and milliseconds of each of the processes that the master thread is running. The goal here is to keep painting and rendering to be at a minimum and the chief thread to exist mostly idle during the animation duration. This volition make sure that our animation code doesn't interfere with other processes that are running on the chief thread which could crusade the blitheness to drop frames or stutter.

Content image Content image
Chief thread activity information during the duration of the blitheness (Chrome devtools)

Safari's Devtools on the other hand provides a slightly different way of looking at this where it displays the activities on the dissimilar threads that are currently running over the duration of the blitheness. We will need to click on Start Timeline Recording from the Develop menu or the red tape button on the meridian left if y'all accept the programmer tools open up. We can then commencement our animation every bit it is recording and finish the recording after we're washed with the animation to analyze the data. This should display a timeline of what is happening during the recorded menstruum. Click on the CPU section of where the animation is happening in the timeline to focus on the thread activities. This volition testify both the main thread usage over time and also a nautical chart of the amount of activities each thread is doing over time. Like to our data assay from Chrome devtools, we want to keep the amount of activity that the primary thread is doing related to our blitheness to a minimum.

Content image Content image
Primary thread activity information during the duration of the animation (Safari devtools)

Boilerplate CPU usage and energy impact

Like master thread processing, we desire to keep average cpu usage at a minimum equally CPU usage has a directly impact on energy bear upon. In other words, the higher the average CPU usage, the college the energy consumption which will consequence in faster draining of batteries.

We can use Safari's timeline feature to view the CPU usage and energy touch over a catamenia of fourth dimension. We will demand to click on the tape push button on Safari's devtools and trigger the blitheness in lodge to analyze it.

If yous look at the image below, you lot can select private sections from the timeline to view what is happening within that fourth dimension period. Clicking on the 'CPU` option on the left will brandish some additional details nearly the master thread usage and its energy bear on. These are properties that nosotros desire to keep at a minimum. For energy impact, nosotros desire the needle in the energy impact dial to autumn closer to the left stop towards the green (low) and go along the 'Average CPU' usage at a low percentage.

Something to note here is that in a existent application there could be a lot of other processes running that would affect these metrics which are unrelated to the animation code itself, and so it's important to attempt to isolate the process which are animation specific or test the blitheness code independently to get an accurate measurement.

Content image Content image
Average CPU usage and energy bear upon (Safari devtools)

Safari's develop menu might exist hidden by default, and so if you don't see a 'Develop' option on your menu bar, you will take to become to 'Preferences > Advanced' and cheque the pick to 'Show Develop menu in menu bar'.

Debugging

Both Chrome and Firefox'southward devtools come up with some powerful animation debugging tools which are really handy to use equally we build out animations. These tools let you tiresome down, replay and audit the source lawmaking for your animation. Both browsers' devtools also lets you modify some of the animation properties on the fly and replay your animation with the modified properties.

Chrome

Chrome has two main features that in my opinion are very helpful when it comes to debugging your animation lawmaking. I usually always find myself doing some fine tuning of the animations through the devtools and copying that code over to my lawmaking editor.

Animation Inspector

Earlier nosotros dive into how to utilise the Chrome's animation inspector, let me show you lot where you can discover this astonishing tool. Chrome has the animation inspector choice under the more tools submenu option.

Content image Content image
Where to find Chrome's Blitheness Inspector

At the time this post is written, Chrome just supports CSS animations, CSS transitions, and web animations. You wouldn't exist able to utilise this if y'all are using `requestAnimationFrame` for your animation.

If you have the animations tab open on your devtools, yous should be seeing blocks of animation groups added to the top as your blitheness gets triggered on your application. Clicking on the block will open up a more detailed view of what the actual animations that are existence executed equally shown in the image below.

Let's pause downwards the animations tab view further and discuss some of the key features

  • Controls - lets you play, pause and change the speed of the blitheness
  • Animation groups - shows the different group of animations that were executed. The animation inspector groups the animations based on offset time (excluding delays) predicting which animations are related to each other. From a code perspective, animations that are triggered in the same script block are grouped together.
  • Scrubber - you can drag the red vertical bar left and right to display the state of the blitheness at that time in the timeline
  • Timeline - shows a breakdown of the elements in the DOM that are beingness animated in the animation group and the timeline for each element'southward animation
  • 2 solid circles- these 2 circles mark the beginning and end of the animation. It's possible to see multiple instances of these for cases where the animation runs for multiple iterations, where these solid circles volition mark the first and end of each iteration
  • Highlighted section - the animation duration
  • Hollow circle - timing of keyframe rules if the animation defines whatever (run across 2d to 5th element in the epitome below)

All the components in the timeline for each element can be modified by dragging them horizontally. We can modify the elapsing past moving the start and end solid circles, add delays by moving the highlighted section and modify keyframe timings by moving the hollow circle. We can then view the updated animation changes by clicking on the replay button to rerun the blitheness grouping.

Content image Content image
Animations inspector (Chrome devtools)

Bezier Curve Editor

If you are using CSS keyframes in your animation (this is covered in the afterwards sections of the postal service), Chrome devtools also has a tool to edit the curves of your animation dynamically using Lea Verou's cubic bezier visualization.

This is extremely helpful equally you no longer have to go dorsum and forth betwixt your editor and your browser to tweak the bezier curves to get the right timing, you can do it all in your browser. Employ the replay button on the animations tab to replay the animation with the updated bezier curve. Access this characteristic past clicking on the squiggly line icon on the blitheness property of your element. Below is an image of how to access the bezier bend from your animation.

Content image Content image
Bezier bend editor for keyframe animations (Chrome devtools)

The purple circles attached to the royal lines on the bezier curve editor are draggable vertically and horizontally to edit the curve of the line which in turn will update the cubic-bezier function. You can meet a quick visualization of what the timing function looks like from the purple circle towards the summit of the popup, showing how the animation volition accelerate/decelerate over time.

Firefox

Firefox's devtools has nigh identical functionality as chrome's devtools in terms of its animation inspector and bezier curve editor. I won't become in item on how each of these works since it is more or less covered in the previous section, however, I will add a couple of screenshots of how these look on Firefox's devtools so yous become an thought of what to wait when using Firefox to debug your animations.

Content image Content image
Animation inspector (Firefox devtools)
Content image Content image
Bezier curve editor for keyframe animations (Firefox devtools)

Culling to Angular's Animation Module

Likewise Angular's animation module, Angular likewise gives you the flexibility to use a couple unlike means to write your animation. Some of these are slightly modified common patterns you would see in a regular vanilla application whereas some are more than Angular specific.

Grade based animations

Since angular runs on browsers and utilizes HTML and CSS, we can leverage CSS animations in our Athwart awarding which works the exact same way equally how it works in a vanilla HTML CSS application. You would add a class to an element based on a certain status which volition then trigger an blitheness using CSS either through CSS transitions or keyframes.

The CSS code for both cases will be identical and could be something as simple as the post-obit for CSS transform:

            

<> Re-create

#targetElement { transition : all 0.5 southward ; } #targetElement .shrink { transform : scale ( 0.eight ) ; }

and the following for CSS keyframes:

            

<> Copy

#targetElement .shrink { animation : shrink i s ; } @keyframes shrink { 0% { transform : scale ( 1 ) ; } 100% { transform : calibration ( 0.8 ) ; } }

The chief departure hither is how you can easily add and remove classes using athwart. For case, allow's say we want to add a class called 'shrink` when the isSelected boolean is truthful, in Javascript, it would look something similar this:

            

<> Copy

var element = document . getElementById ( "targetElement" ) ; if (isSelected) { element. classList . add together ( "shrink" ) ; // to add together a class } else { element. classList . remove ( "shrink" ) ; // to remove a class }

This can be handled directly in the template using Angular by attaching a condition to the class. Below is a sample of how it would expect in an Angular template:

            

<> Re-create

<div [course.shrink] = "isSelected" > </div >
Content image Content image
Demo class based animation

Similar to Angular animation's @animation.done event, form based animation as well comes with some events which nosotros can hook into. Depending on whether you are using keyframes or transitions, we can use either animationend or transitionend to mind to the animation cease event.

The nice thing about this approach is that you would exist able to use any CSS blitheness library that works based on calculation and removing classes such equally breathing.css or magic.css. Chris Coyier has an astonishing commodity that lists some of the popular ones if you are interested.

Inline Animations

Basically the same as the class based animation with the exception that the animation code itself is written in the template instead of every bit function of a grade in the CSS file. This is peculiarly useful if you have some parts of the animation code that needs to exist dynamic in a mode where a sure transformation value needs to be calculated based on some predetermined external factor. An example of this would be if nosotros want to add a scale with a different value depending on the element's alphabetize. We can exercise this by binding the transform with a function which returns a string which contains the calculated value.

            

<> Re-create

<div [fashion.transition] = " '0.5s' " [style.transform] = "isScaledDown ? getScaleDown(index) : getResetScale()" > </div >
            

<> Re-create

isScaledDown = simulated ; getScaleDown (index: number ) : string { return ` scale( ${ i - (index + ane ) / ten } ) ` ; } getResetScale ( ) : string { return 'scale(ane)' ; }
Content image Content image
Demo inline animation

Web Animation APIs

Another way to add animation to your awarding is to use regular web animation APIs (WAAPI). WAAPI at the time this was written is supported past Firefox 48+ and Chrome 36+, but it has a comprehensive and robust polyfill, making it usable in product today, even while browser back up is limited. Like to class based and inline animations, utilizing WAAPI in Angular is very similar to how regular Javascript handles it, the master difference existence how we access DOM elements.

In patently HTML and Javascript nosotros would typically give the element an id and use document.getElement.byId with the element's id to get reference to the DOM chemical element. In Athwart, nosotros can use the template reference variable (#) instead and get it's reference by using the ViewChild decorator.

Lets first define the animation and the timing of the animation which we could use in both of our examples

            

<> Re-create

getShakeAnimation ( ) { return [ { transform: 'rotate(0)' } , { transform: 'rotate(2deg)' } , { transform: 'rotate(-2deg)' } , { transform: 'rotate(0)' } , ] ; } getShakeAnimationTiming ( ) { render { duration: 300 , iterations: three , } ; }

The next ii sets of snippets are how you would employ the blitheness higher up in a HTML and Javascript application followed by how a slight variation of the aforementioned code can be used in an athwart project.

html and js

            

<> Copy

<div id = "targetElement" > </div >
            

<> Copy

document . getElementById ( 'targetElement' ) . animate ( this . getShakeAnimation ( ) , this . getShakeAnimationTiming ( ) ) ;

In an angular awarding

            

<> Copy

<div #targeElement > </div >
            

<> Copy

@ ViewChild ( 'targetElement' ) targetElement: ElementRef ; this . targetElement . nativeElement . breathing ( this . getShakeAnimation ( ) , this . getShakeAnimationTiming ( ) ) ;

Note that the animation part in both the snippets are exactly the same!

Content image Content image
Demo web animations API

The web animations API likewise comes with some handy utility properties and functions that nosotros tin use in our Angular awarding the same mode you lot would practise in a regular vanilla awarding such as cancel to cancel the current animation and some fundamental result listeners such as oncancel and onfinish. Here is a link to the bachelor APIs.

Aspect Directive and Animation Builder

As described by the official documentation, an aspect directive is a means to 'change the advent or beliefs of a DOM chemical element', making this a peachy mode to handle a more complex animations (in terms of what triggers the animations). The nice affair well-nigh using directives is that it gives you an piece of cake manner to access the element in the DOM that the directive is applied to - letting us manipulate information technology the same fashion nosotros would in a component, and as well adhere HostListenerdue south to listen to whatever events and react to the emitted effect.

However, unlike edifice out a custom component with the animations and reusing the components, a directive lets you attach just the behavior to any element in your awarding. It makes it more than flexible if nosotros want to reuse the same animation beyond dissimilar elements or components.

Directives don't have the animations assortment every bit part of the decorator, so we will have to apply angular'south AnimationBuilder to build the blitheness and AnimationPlayer to play the animation. Here is an example of an animation fading out the chemical element on mouse down and fading information technology dorsum in on mouse upwardly.

            

<> Copy

import { Directive , HostListener , ElementRef } from '@athwart/cadre' ; import { AnimationBuilder , AnimationMetadata , style, breathing, } from '@angular/animations' ; @ Directive ( { selector: '[appfadeMouseDown]' , } ) consign form FadeMouseDownDirective { @ HostListener ( 'mousedown' ) mouseDown ( ) { this . playAnimation ( this . getFadeOutAnimation ( ) ) ; } @ HostListener ( 'mouseup' ) mouseUp ( ) { this . playAnimation ( this . getFadeInAnimation ( ) ) ; } constructor ( individual builder: AnimationBuilder , private el: ElementRef ) { } private playAnimation (animationMetaData: AnimationMetadata [ ] ) : void { const animation = this . architect . build (animationMetaData) ; const player = animation. create ( this . el . nativeElement ) ; role player. play ( ) ; } individual getFadeInAnimation ( ) : AnimationMetadata [ ] { return [ animate ( '400ms ease-in' , way ( { opacity: i } ) ) ] ; } individual getFadeOutAnimation ( ) : AnimationMetadata [ ] { return [ animate ( '400ms ease-in' , style ( { opacity: 0.5 } ) ) ] ; } }

You could besides respond to keyboard events such as specific key presses past replacing what events your HostListener is listening to. For example, @HostListener('document:keydown.escape', ['$event']) would get triggered when you printing on the escape key.

Now that we have the directive build out, we can employ it past adding information technology's selector to the element in our template like this:

            

<> Copy

<div appfadeMouseDown > ... </div >
Content image Content image
Demo using attribute directive to trigger animations

Directives as well permit y'all laissez passer in custom inputs so we could add some parameters from the component that uses the directives to gear up some configuration or toggle the state. I won't go into much particular on that since the official documentation has a lot of great examples on how that works.

Closing Thoughts

Making an animation work well in your awarding whether in the context of Angular or not, oftentimes goes beyond just writing the code. The process usually includes a lot of tweaks and refining, debugging and profiling, and testing it out on multiple different browsers for consistency and performance.

Hopefully what I covered in this post helps give you lot some more insight on what goes on behind the scenes when you write animation code and besides provide a few more tools to assistance create cute and performant animations.

Animations are a great add-on to your application, making information technology more than interactive and fun to use, and Athwart gives you a powerful tool out of the box to create beautiful and complex animations, each having its individual pro and cons and use cases. With that said, I desire to end this post with a some things I endeavor to remind myself every fourth dimension I add together animations to my awarding:

  • should be unproblematic and quick
  • have a purpose such as guiding the user or conveying some kind of message
  • not a distraction or a barrier to be able to use your awarding rapidly

DOWNLOAD HERE


Animate On Triggering In Wrong Places Router

Posted by: jacksonthersellse.blogspot.com

0 Response to "Animate On Triggering In Wrong Places Router"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel