Implementing motion using Angular

Gerard Sans | @gerardsans

Google Developer Expert

Google Developer Expert

International Speaker

Spoken at 89 events in 25 countries

Blogger

Blogger

Community Leader

900

1.5K

Angular Trainer

Master of Ceremonies

Master of Ceremonies

Angular Academy

Why introduce motion?

Improved UX ✨

Perceived Performance

Immersive interactions

Better engagement

User happiness 😃

Animation Principles

Squash & Stretch + Anticipation

Follow through

Exaggeration

Composition

CSS Animations

DURATION

0%

100%

CSS Animations

KEYFRAMES

CSS Animation

NAME

animation:  fade  5s 1s infinite linear;

DURATION

DELAY

ITERATIONS

TIMING

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

timing function/easing

60 FPS

Smooth 60FPS

60 FPS

Time

16.66ms

16.66ms

16.66ms

1000/60ms

paint

frame

paint

frame

paint

frame

😃

paint

frame

paint

frame

Losing Frames!

60 FPS

Time

16.66ms

16.66ms

16.66ms

😱

Motion Techniques

Move

transform: translateX(10px);

Resize

transform: scale(0.5);

Rotate

transform: rotate(1turn);

Fade

opacity: 0;

Move

<div class="circle base elastic"></div>

.base {
  animation: move 2s infinite;
}
.elastic { 
  animation-timing-function: 
    cubic-bezier(.8,-0.6,0.2,1.5);
}

@keyframes move {
  0% { 
    transform: translateX(-250px);
  }
  40%, 60% { 
    transform: translateX(50px);
  }
  100% { 
    transform: translateX(250px);
  }
}

Rotate

<div class="square base linear"></div>

.base {
  animation: spin 2s infinite;
}
.linear {
  animation-timing-function: linear;
}

@keyframes spin {
  to { 
    transform: rotate(1turn);
  }
}

Resize

<div class="circle base"></div>

.base {
  animation: size 2s infinite;
}

@keyframes size {
  0%, 40%, 100% {
    transform: scale(1);
  }
  25%, 60% {
    transform: scale(1.1);
  }
}

Fade

<div class="circle base elastic"></div>

.base {
  animation: fade 2s infinite;
}

@keyframes fade {
  0% { 
    transform: translateX(0px);
    opacity: 0;
  }
  40%, 60% { 
    transform: translateX(80px);
    opacity: 1;
  }
  100% { 
    transform: translateX(0px);
    opacity: 0;
  }
}

Angular Animations

States & Transitions

fadeIn

fadeOut

Fading animation

TRANSITIONS

STATE

STATE

fadeIn => fadeOut

fadeOut => fadeIn

opacity: 1

opacity: 0

animate('400ms linear')

animate('400ms linear')

fadeOut <=> fadeIn

animate('400ms linear')

Fading an Element

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';
  }
}

Fading Animation

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

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

Dynamic Animations

Builder

`(${x}, ${y})`

(100, 50)

Player

Template

1

2

(100, 50)

3

build()

play()

AnimationBuilder

import {AnimationBuilder} from "@angular/animations";
export class MyComponent {
  constructor(
    private elem: ElementRef, 
    private builder: AnimationBuilder
  ) {
    const player = this.playerMoveTo({ x: 100, y: 100 });
    player.play();
  } 
  playerMoveTo(to) {
    const moveAnimation = this.builder.build([
      animate('1s', style({ transform: `translate(${x}px, ${y}px)` }))
    ]);
    return moveAnimation.create(this.elem.nativeElement
      , {params: { x: to.x, y: to.y }});
  }
}

Tracking mouse position

constructor(
  private elem: ElementRef
) {
  // find out center
  this.mx = elem.nativeElement.offsetWidth/2;
  this.my = elem.nativeElement.offsetHeight/2;
} 

ngOnInit() {
  const mouseMove$ = fromEvent(document, 'mousemove')
    .pipe(map(e => ({ x: e.pageX, y: e.pageY })));

  mouseMove$.subscribe({
    next: e => {
      this.lastPosition = { x: e.x-this.mx, y: e.y-this.my };
      const player = this.playerMoveTo(this.lastPosition);
      player.play();
    }
  })
}

Motion Design

 Principles

Easing

Offset Delay

Parenting

Binding Values

Overlay

Applying

Motion

Example

Adding Motion

void

*

Animating new items

void => *

STATE

STATE

:enter

scale(1)

opacity 1

scale(0.5)

opacity 0

*

void

Animating removed items

* => void

STATE

STATE

:leave

scale(0.5)

opacity 0

scale(1)

opacity 1

Execution Order

sequence

group

time

Dynamic Selectors

class="container"

class="item"

class="item"

query(selector)

Dynamic Selectors

query('.item')

class="container"

class="item"

class="item"

Dynamic Selectors

query('.item:enter')

class="container"

class="item"

class="item"

class="item"

added

Composition

animateChild

time

Stagger

time

More

Influencers

David Khourshid

Issara Willenskomer

Sarah Drasner

Rachel Nabors