Animations

Advanced

Angular

Elad Bezalel

Angular Animations Core Team

Angular & Web Technologies GDE

Why bother?

CSS Animations

Easiest & Fastest

CSS Transitions

Animating the visual change between element style states

button {
  background: blue;
  color: white;
  padding: 50px;
  font-size: 50px;
}

button:active {
  background: green;
  transition: background 300ms ease-in;
}
button {
  background:blue;
  color:white;
  padding:50px;
  font-size:50px;

  /* animation css here */
  transition:300ms ease-in;
}

button:active {
  background:green;
}

button:hover {
  background:orange;
}

CSS Keyframes

Reuse a visual change no matter the state

@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.loading-indicator:before {
  animation:800ms rotate infinite ease;
}
@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.loading-indicator:before {
  animation:800ms rotate infinite ease;
}

CSS Animations

Pros

  1. Benefits Cascading
  2. Consistent API
  3. Good for simple animations
.gallery-entry .picture {
  transition:300ms ease-out;
}

.gallery-entry:hover .picture {
  transform:scale(1.2);
}

.gallery-entry.active .picture {
  transform:scale(1.3);
}

#gallery {
  transition:500ms all;
}

@media only screen and (max-width: 600px) {
  #gallery {
    height:calc(200vh - 100px);
    grid-template: repeat(2, 50fr);
  }
}

CSS Animations

Cons

  1. Very difficult to sequence
  2. difficult to intercept and manipulate
  3. No dynamic values
  4. Runs on the CPU*

Javascript Animations

Tricky and complex

Why Javascript?

  1. More control than CSS Animations
  2. More intuitive than CSS Animations
  3. Build Complex Choreographies

GreenSock

GSAP

  1. More control than CSS Animations
  2. More intuitive than CSS Animations
  3. Animate Anything
  4. Faster than CSS3 animations and transitions
  5. Build Complex Choreographies
  6. Works with SVG
  7. Solve browser compatibility issues
  8. 20x faster than jQuery
  9. 7 million sites use GSAP

Pros

GSAP Example

export function zoomElement(element, onComplete) {
  const { content, picture, title } = getElements(element);
  
  return new TimelineLite()
    .set(content, { ... })
    .set(picture, { ... })
    .set(title,   { ... })
    .add('start')
    .to(content, 0.5, { ... }, 'start')
    .to(picture, 0.3, { ... },'start')
    .to(title,   0.3, { ... }, 'start')    
    .eventCallback('onComplete', onComplete );
}

Declarative Timelines

GSAP

  1. Not as popular as CSS3
  2. Costs money
  3. Less learning resources

Cons

Combine JS + CSS!

Angular Animations

Best of all solutions

Angular issues with GSAP

  1. Manipulates DOM elements directly
  2. ​Not Angular Native
  3. Not aware of Router and transitions
  4. Not aware of ngIf, ngFor, ngSwitch

Angular Animations

  1. More control than CSS Animations
  2. Using CSS transitions and keyframes
  3. Animation DSL
  4. Build Complex Choreographies
  5. Works with SVG
  6. Solve browser compatibility issues
  7. Works with enter & leave

Pros

Simple Animations

Style Animations

<div class="open-close-container"
     [style.background]="isOpen ? 'yellow' : 'red'"
     (click)="isOpen = !isOpen">
     <span>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</span>
</div>
.open-close-container {
  transition: background .3s ease;
}

Class Animations

<div class="open-close-container"
     [class.opened]="isOpen"
     (click)="isOpen = !isOpen">
     <span>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</span>
</div>
.open-close-container {
  transition: background .3s ease;
  background: red;
}

.open-close-container.opened {
  background: yellow
}

More complex animations

With the DSL

@Triggers

@Component({
  animations: [
    trigger('myAnimation', [
      //...
    ])
  ]
})
<div [@myAnimation]="myValueExpression"></div>

States

trigger('myAnimation', [
  state('open', style({
    height: '200px'
  })),
  state('closed', style({
    height: '0px'
  }))
})

Transitions

trigger('myAnimation', [
  // ...	
  transition('open => closed', [
    animate('.5s ease')     
  ])
})

A "simple" open-close animation

trigger('openClose', [
  state('open', style({
    height: '200px',
    opacity: 1,
    backgroundColor: 'white'
  })),
  state('closed', style({
    height: '*',
    color: 'white',
    backgroundColor: '#3F51B5'
  })),
  transition('open => closed', [
    animate('.5s ease')
  ]),
  transition('closed => open', [
    animate('.2s ease')
  ]),
])

Enter & Leave Animations

<div *ngIf="myExpression" [@myAnimation]="expression"></div>
trigger('myAnimation', [
  state('void', style({
    height: '0px',
    opacity: 0,
  })),
  state('*', style({
    height: '*',
    opacity: '*',
  })),
  transition('open <=> closed', [
    animate('.5s ease')
  ]),
])

Enter & Leave Animations

<div *ngIf="myExpression" @myAnimation></div>
trigger('myAnimation', [
  transition(':enter', [
    style({ transform: 'translateX(-50px)'}),
    animate('300ms ease-out', style({ transform: 'translateX(0px)'}))
  ]),
  transition(':leave', [
    animate('300ms ease-out', style({ transform: 'translateX(50px)'}))
  ]),
])

Choreographing

trigger('myMultiElementAnimation', [
  transition('* => start', [
    query('.some-class', [
      animate(/*...*/)
    ]),
    query('img', stagger(50, [
      animate(/*...*/)
    ]))
  ])
])

Routing Animations

<div class="router-container"
 [@routerAnimation]="prepareRouteState(outlet)"
 (@routerAnimation.start)="scrollToTop()">
  <router-outlet #outlet="outlet"></router-outlet>
</div>
export const ROUTES = [
  {path:'', component: HomePageComponent, data: { animation: 'home' }},
  {path:'profile/:id', component: ProfilePageComponent, data: {animation: 'profile'}}
]
@Component({
  //...
  animations: [
    trigger('routerAnimation', [
      // this will fire when the home page changes to the profile page and vice versa
      transition('home <=> profile', [
        style({ position: 'relative' }),
        // the new routed page is first hidden
        query(':enter', [
          style({position:'absolute', opacity:0, top:'-100%' })
        ]),
        // then we animate away the old page
        query(':leave', [
          style({ position: 'absolute', top:0 }),
          animateChild(),
          style({ opacity:0 }),
        ]),
        // then we animate back in the new page
        query(':enter', [
          style({opacity:1, top:'0%'}),
          animateChild()
        ]),
      ]) 
    ])
  ]
})
class AppComponent {...}

Choreographing

Altogether

animate pipe

The future

@Elad_Bezalel

Thanks!

Advanced Angular Animations

By eladbezalel

Advanced Angular Animations

  • 1,561