Angular

Animations (v4.2+)

slides.com/gerardsans |  @gerardsans

Text

Google Developer Expert

Master of Ceremonies

Blogger

International Speaker

Angular Trainer (v4+)

Community Leader

850

950

Angular In Flip Flops

CSS Introduction

Cascading Style Sheets

  • Styling HTML Elements
  • CSS rules
  • Specificity and order
  • Box model

HTML Page life-cycle

source: blog

CSS Rule

SELECTOR

a.active:hover {

       color: #333;

}

PROPERTY

VALUE

DECLARATION

pseudo classes/elements

:hover

:link

:active

:target

:not(selector)

:focus

::first-letter

::first-line

::before

::after

::selection

Specificity and Order

  • Element style

  • Element id

  • class/attribute selectors

  • element selectors

  • last CSS rule wins

Box Model

source: blog

Animations

Introduction

Web Animations API

Background

  • Integrate CSS, JS and SVG
  • CSS hardware acceleration
  • CSS variables + JS
  • requestAnimationFrame (rAF)
  • setInterval
  • Velocity, GreenSock (GSAP)

source: blog

index.html

<html>
  <head>
    <script src="https://unpkg.com/web-animations-js@2.2.5"></script>
    ...
  </head>

  <body>
    <my-app>
    loading...
    </my-app>
  </body>
</html>

Angular CLI 1.0

npm install web-animations-js --save

// src/polyfills.ts
import 'web-animations-js';

CSS Transitions

DURATION

START

END

CSS Transitions

CSS Transitions

  • Define an initial and final state
  • Intermediate states are calculated automatically
  • We can choose which CSS properties we want to affect
  • Not all CSS properties are animatable (list)

Animatable CSS

all background* border* bottom box-shadow clip clip-path color filter font* height left margin* mask* offset* opacity outline* padding* perspective* right text-decoration text-shadow top transform vertical-align visibility width z-index

transition

PROPERTY

transition: color 5s ease-in 1s;

DURATION

TIMING

DELAY

timing function/easing

CSS Animations

DURATION

0%

100%

CSS Animations

KEYFRAMES

CSS Animations

  • Define any number of states between the initial and final state
  • Changes from states are calculated automatically
  • We can choose which CSS properties we want to affect

CSS Animation

NAME

animation:  fade  5s 1s infinite linear;

DURATION

DELAY

ITERATIONS

TIMING

CSS Animation

@keyframes fade {
  0%     { opacity: 1; }
  100% { opacity: 0; }
}

CSS Animation

@keyframes fade {
  from   { opacity: 1; }
  to        { opacity: 0; }
}

Angular Animations

fadeIn

fadeOut

States & Transitions

TRANSITIONS

STATE

STATE

fadeIn => fadeOut

fadeOut => fadeIn

fadeIn <=> fadeOut

States & Transitions

void

*

Special Keywords

void => *           :enter

* => void          :leave

void <=> *

STATE

STATE

Animations Setup

// npm install --save @angular/animations
// app.module.ts

import {Component, NgModule} from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@Component({ })
export class App { }

@NgModule({
  imports: [ BrowserModule, BrowserAnimationsModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule)

Animation

Animation Example

import {fade} from './animations';

@Component({
  selector: 'my-app',
  template: `<button [@fade]='fade' (click)="toggleFade()">Fade</button>`,
  animations: [ fade ]
})
export class App {
  fade = 'fadeIn';
  toggleFade(){
    this.fade = this.fade === 'fadeIn' ? 'fadeOut' : 'fadeIn';
  }
}

Animation Example

import { 
  trigger, transition, state, style, animate 
} from '@angular/animations';

export const fade = trigger('fade', [
  state('fadeIn', style({ opacity: 1 })),
  state('fadeOut', style({ opacity: 0.1 })),
  transition('fadeIn <=> fadeOut', animate('2000ms linear'))
]);

demo

Keyframes

Keyframes Example

import { 
  trigger, transition, state, style, animate, keyframes 
} from '@angular/animations';

export const grow = trigger('grow' , [
  state('small', style({ transform: 'scale(1)' })),
  state('large', style({ transform: 'scale(1.4)' })),
  transition('small <=> large', animate('200ms linear'))
])

Keyframes Example

import { 
  trigger, transition, state, style, animate, keyframes 
} from '@angular/animations';

export const grow = trigger('grow' , [
  state('small', style({ transform: 'scale(1)' })),
  state('large', style({ transform: 'scale(1.4)' })),
  transition('small <=> large', animate('200ms linear', keyframes([
      style({transform: 'scale(1.6)', offset: 0}),
      style({transform: 'scale(1.4)', offset: 1})
    ])
  ))
])

Animation

Callbacks

Animation Callbacks

import {fade} from './animations';

@Component({
  selector: 'my-app',
  template: `<img [@fade]='fade' 
    (@fade.start)="start($event)" 
    (@fade.done)="done($event) >`,
  animations: [ fade ]
})
export class App {
  start(e) { }
  done(e) { }
}

Callback Event

  $event == {
    element: any,        // div element
    triggerName: string, // "fade"    
    phaseName: string    // "start" or "done"
    fromState: string,   // "in" or "out"
    toState: string,     // "in" or "out"
    totalTime: number,   // 1000 (milliseconds)
  }

demo

New in Angular 4.2

Animations

Orchestration

Animate in Parallel

export const revealGroup = trigger('revealGroup', [
  transition(':enter', [
    query('*', style({ opacity: 0 })),
    group([
      query('*', animate(2000, style({ opacity: 1 }))) 
    ])
  ])
]);

Animate in Sequence

export const revealGroup = trigger('revealGroup', [
  transition(':enter', [
    query('*', style({ opacity: 0 })),
    sequence([
      query('*', animate(2000, style({ opacity: 1 }))) 
      query('*', animate(1000, style({ color: 'red' }))) 
    ])
  ])
]);

query + stagger

export const revealStagger = trigger('revealStagger', [
  transition(':enter', [
    query('*', style({ opacity: 0 })),
    query('*', stagger(1000, [
        animate(2000, style({ opacity: 1 }))
      ])
    )
  ])
]);

demo

query + animateChild

export const reveal = trigger('reveal', [
  transition(':enter', [
    query('@fade', [
      animateChild()
    ]),
    query('@*', [
      animateChild()
    ]),
  ])
]);

Router Transitions

Router transitions

import { routerAnimations } from './router.animations';

@Component({
  template: `
    <div [@routerAnimations]="routeTransition(outlet)">
      <router-outlet #outlet="outlet"></router-outlet>
    </div>
  `,
  animations: [ routerAnimations ]
})
export class App { 
  routeTransition(outlet) {
    const routeAnimation = outlet.activatedRouteData['animation'] || {};
    return routeAnimation['value'] || null;
  }
}

Routes Setup

const routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: Home, 
    data: {
      animation: { value: 'home' }
    } 
  },
  { path: '**', component: NotFound }
];

Router Animations

export const routerAnimations = trigger('routerAnimations', [
  transition('about <=> home', [
    query(':enter, :leave', style({ position: 'fixed', width:'100%' })),

    group([
      query(':enter', [
        style({ transform: 'translateX(100%)' }),
        animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))
      ]),
      query(':leave', [
        style({ transform: 'translateX(0%)' }),
        animate('0.5s ease-in-out', style({ transform: 'translateX(100%)' }
      ]),
    ])
  ])
])

demo

Performance

Considerations

  • Avoid unnecessary layout/repaint
    • Use opacity, transform
  • Hardware acceleration (avoid)
    • transform: translateZ(0)

Saving Resources

.visible.advert {
  will-change: transform;
}

.visible.advert:hover {
  transform: scale(1.2);
}

Why use Animations?

Benefits

  • Improved UX ✨
  • Immersive interactions
  • Better engagement
  • User happiness 😃 

Thanks!