ngAnimate2 = Animations + Layouts

Matias Niemelä (@yearofmoo)

matias [at] yearofmoo [dot] com
ng-europe 2016

NG1 / NG2

Matias

yearofmoo.com

Demos & Slides

Link to Slides

yom.nu/ng2eu-2016

Code Examples

github.com/matsko/ng2eu-2016-code

ngAnimate = Animations + Layouts

Let's talk animations

Let's talk layouts

Animations

Layout-driven

Visual cues

CSS or web-animations

Angular defines animations

Layouts

Foundation of UI

The result of CSS + HTML

Angular manipulates layout

Angular Animation Basics

Animations in Angular 2

Defined inside @Component

DSL (Domain Specific Language)

Layout / Component Triggers

State-Based

NG2 Animations 101

@Component({ animations: [ trigger('myAnimation', [ /* ... */ ]) ], template: `<div [@myAnimation]="state">...</div>` }) class Cmp {}

The Animation DSL

Functional Composition

Animations are Platform Agnostic

AoT-friendly

No CSS Transitions/Keyframes

CSS-based animations...

Are tricky to detect

Limited expressiveness

Hard to program

DOM dependent

Web-Animations API

Web-animations are native-level

Programmatic, performant

Browser Support
- Chrome & Firefox
- Safari (TP), Edge (hopefully soon)

Angular uses the web-animations.js polyfill

Web Animations 101

element.animate([ { opacity: 0 }, { opacity: 1 } ], 1000)

Web Animations are strict

No CSS detection

Start/End styles must be defined

Transforming animations is hard

One element at a time

The NG2 Animation DSL...

Makes use of web-animations

That makes animations flexible

Via framework essentials

Integrated into @Component

Sample: web-animation

element.animate([ { opacity: 0 }, { opacity: 1 } ], 1000)

Sample: NG2 Animation DSL

trigger('myAnimation', [ transition('* => *', [ style({ opacity: 0 }) animate(1000, { opacity: 1 }) ]) ])

Benefits of the DSL

Component-Centric

Declarative Syntax

Platform Independence

Blackbox Implementation Details

Testing & Performance

Animations are invoked...

<!-- 1. template bindings --> <div @trigger></div> <div [@trigger]="state"></div> <!-- 2. host bindings --> @Component(...) class Cmp { @HostBinding('@trigger') public state; }

Sample: List of stuff

@Component(...) class MonthListCmp { months = ['janvier', 'février', ...] }

Sample: List of stuff

<div *ngFor="let month of months" class="month" @monthAnimation> {{ month }} </div>

Sample: List of stuff

// inside of @Component animations: [ trigger('monthAnimation', [ transition(':enter', [ style({ opacity: 0, width: 0 }), animate(500, style({ width: '20%', opacity: 1 })) ]), transition(':leave', [ animate(500, style({ opacity: 0, width: 0 })) ]) ]) ]

1

Sample: Open Close Component

@Component({ ... }) class OpenCloseCmp { open = false; // used within template toggle() { this.open = !this.open; } }

Sample: Template Bindings

<!-- component-tpl.html --> <button (click)="toggle()"> Open / Close </button> <div [@slideOpen]="open ? 'open' : 'closed'"> this container is animated! </div>

Sample: Animation Definition

@Component({ templateUrl: 'component-tpl.html', animations: [ trigger('slideOpen', [ state('open', style({ height: '*' })), state('closed', style({ height: 0 })), transition('closed <=> open', [ animate('500ms ease-out') ]) ]) ] })

2

(@animation.callback)

Capture the start/end of animations

(@animation.start)="onStart($event)"

(@animation.done)="onDone($event)"

Sample: Animation Callbacks

<div [@slideOpen]="open ? 'open' : 'closed'" (@slideOpen.start)="slideStarted($event)" (@slideOpen.done)="slideDone($event)"> this container is animated! </div>

3

How it Works

Animation Pipeline

Prep: Parsing, Instruction Building

Run: Platform Animation Calls

Cleanup: Teardown and reset

Preparation Phase: Parsing

Animations + CSS are parsed

Errors are reported

DSL code is optimized/completed

AST (Abstract Syntax Tree) constructed

Preparation Phase: Compilation

The AST is turned into code

This means machine-JS code

Fast, optimizable, basic

AoT friendly!

Sample: Animation DSL

trigger('slideOpen', [ state('open', style({ height: '*' })), state('closed', style({ height: 0 })), transition('closed <=> open', [ animate('500ms ease-out') ]) ])

Sample: AoT Animation Output

var App_slideOpen_states = {'*': {}}; function App_slideOpen_factory(view,element,queries,currentState,nextState) { view.cancelActiveAnimation(element,'slideOpen',(nextState == 'void')); var collectedStyles = {}; var player = null; var totalTime = 0; var defaultStateStyles = App_slideOpen_states['*']; var startStateStyles = App_slideOpen_states[currentState]; if ((startStateStyles == null)) { (startStateStyles = defaultStateStyles); } var endStateStyles = App_slideOpen_states[nextState]; if ((endStateStyles == null)) { (endStateStyles = defaultStateStyles); } jit_renderStyles11(element,view.renderer,jit_clearStyles12(startStateStyles)); if ((player == null)) { (player = new jit_NoOpAnimationPlayer13()); } player.onDone(function() { jit_renderStyles11(element,view.renderer,jit_prepareFinalAnimationStyles14(startStateStyles,endStateStyles)); }); view.queueAnimation(element,'slideOpen',player,totalTime,currentState,nextState); }

Web Workers

Machine code runs in thread

UI animation code is called

Angular does this automatically

Features available today

style(data)

// supported now style({ opacity: 0 }) // very soon! style('.invisible')

animate(time, style)

animate("1s", style(...)) animate("1s", keyframes([ style({ ... offset: 0 }), style({ ... offset: 0.5 }), style({ ... offset: 1 }), ])

Animation Auto Styles

transition('* > *', [ style({ height: 0 }), animate("1s", style({ height: '*' )) ])

animation timing

animate(1000, ...) animate("1s", ...) animate("1s 500ms", ...) animate("1s 500ms ease-out", ...) animate("1s 500ms cubic-bezier(.29,.55,.53,1.53)", ...)

sequence()

// [] = sequence() transition('* > *', [ animate(...), animate(...) ])

group()

transition('* > *', [ style({ height: 0 }), group([ animate(500, style({ transform: 'rotate(360deg)' })) animate("1s ease-out", style({ height: '*' })) ]) ]))

Animation Cancel

Animation Cancel Support

Features coming soon

In the next weeks/months

query() + select()

CSS Parser Feature

Programmatic Player Access

Renderer/JS integration

Finding Elements...

Trigger elements can only be animated

What about animating inner children?

query()

Finds inner elements to animate

Within the component template

Mixes nicely into sequence/group

Sample: query()

transition(':enter', [ query('headerRef, footerRef', style({ opacity: 0 })), group([ query('headerRef', animate("1s", style({ opacity: 1 })) query('footerRef', [ animate("1s 500ms", style({ opacity: 1 })) ]) ])

Sample: query html

<section *ngIf="..."> <header #headerRef> ... </header> <footer #footerRef> ... </footer> </section>

Sample: select()

transition(':enter', [ select('*', style({ opacity: 0 })), group([ select('header', animate("1s", style({ opacity: 1 })) select('footer', [ animate("1s 500ms", style({ opacity: 1 })) ]) ])

4

Styling CSS...

Inline styles are clunky

And not DRY

Very verbose

CSS Parser Feature

External stylesheets can be loaded

Classes and Keyframes can be referenced

Sample: CSS Referencing

@Component({ styleUrls: ['external.css'], animations: [ /* ... */ ] }

Sample: CSS Stylesheet

/* external.css stylesheet */ .invisible { opacity: 0; } .visible { opacity: 1; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

Sample: CSS Referencing

animations: [ trigger('active', [ transition('inactive > active', [ style('.invisible'), animate('1s', [ style('.visible'), keyframes('rotate') ]) ]) ]) ]

Programmatic Player Access

Animations can be injected

Programmatic execution

Programmatic control

Player position access

Sample: Animation Player Access

class Cmp { @Animation('move') public moveAnimation; startAnimation() { var player = this.moveAnimation.start(this.elementRef); player.setPosition(0..100); } }

5

Programmatic Evaluations

JS evaluation is missing

Style calculation / overriding?

External libraries?

External data?

The expr() method

Renderer-level code calls

Basically anything window-level

Animation data gets passed in

Sample: Animation Expressions

animation('rippleAtClick', [ style(expr('eventXY($event)')) animate(1000, keyframes('ripple')) ])

Sample: Expression Definition

// animation.js (outside of angular) window.eventXY = function(event) { return { top: event.client.x, left: event.client.y }; }

Sample: Expression Player

@import {AnimationPlayer} from "@angular/core"; class CustomAnimationPlayer implements AnimationPlayer { /* ... */ } window.customAnimation = element > new CustomAnimationPlayer(element);

Sample: Style Mixing

style([ '.red-color', { height: 100 }, expr('styleMePretty()'), ])

Layouts

Templates drive layout

HTML is the foundation

Angular updates HTML

Bindings change...

Structure changes...

Changes in layout are animated

Dimensional sizing

Structural removals

Decorative / Effects

What does CSS do?

Works with dimensional sizing

Highly dependent on layout type

block, inline, position:absolute?

forget about floats, flexbox and tables

6

What can Angular do?

Use state-based values for layout

Angular can handle the change

7

The end goal

Angular will will mix

Custom animations

Alongside automatic layout animations

And CSS-based effects

Links

Link to Slides

yom.nu/ng2eu-2016

Code Examples

github.com/matsko/ng2eu-2016-code

m
e
r
c
i