Animating Ember

From the beginning

Rob Harper / @rdharper

Jan 9, 2014

Press s to see presentation notes

Why bother? us?

Animation Goals

  • Route Transitions (back button) {{outlet}}
  • Modals ContainerView or {{outlet}}
  • ...any view coming or going CollectionView or {{each}}

Ember.ContainerView


Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
  /**
    If you would like to display a single view in your ContainerView,
    you can set its currentView property. When the currentView property
    is set to a view instance, it will be added to the ContainerView. If
    the currentView property is later changed to a different view, the
    new view will replace the old view. If currentView is set to null,
    the last currentView will be removed.
  */
  currentView: null
});
          

CurrentView Default Behaviour

Old View


            container.removeObject( oldCurrentView );
          

New View


            container.pushObject( newCurrentView );
          

Reimplementing Default Behaviour

Edit this example

Deferring Destruction

Old View


Ember.run.later( function() {
  container.removeObject( oldCurrentView );
}, 2000 );
          

New View


            container.pushObject( newCurrentView );
          

Recall that ContainerView manages an array of views

Deferring Destruction

Edit this example

Fade Away

Old View


oldCurrentView.$().fadeOut( 2000, function() {
  container.removeObject( oldCurrentView );
});
          

New View


            container.pushObject( newCurrentView );
          

Fade Away

Edit this example

Fade In

Old View


oldCurrentView.$().fadeOut( 2000, function() {
  container.removeObject( oldCurrentView );
});
          

New View


container.pushObject(newView);
newView.one('didInsertElement', function() {
  newView.$().hide().fadeIn(2000);
});
          

Fade In

Edit this example

Now What?

Outlets

{{outlet view="myAnimatedContainer"}}
Routes and Modals

Collections

Instead of animating currentView, animate all the views!


Override CollectionView's arrayWillChange and arrayDidChange

Collections

Edit this example

Effects

Whole container...

{{outlet view="myAnimatedContainer" effect="drop"}}

...or by view


MyAnimatedContainer.defineTransition({
  from: 'some-parent-view',
  to: 'some-child-view',
  effect: 'slideLeft',
  duration: 500
});

MyAnimatedContainer.defineTransition({
  from: 'some-child-view',
  to: 'some-parent-view',
  effect: 'slideRight',
  duration: 250
})
            

Effects

Delegate to child views...


FadingView = Ember.Mixin.create({
  reveal: function() {
    var self = this;
    return new Ember.RSVP.Promise( function(resolve, reject) {
      self.$().hide().fadeIn(500, resolve);
    });
  },
  dismiss: function() {
    var self = this;
    return new Ember.RSVP.Promise( function(resolve, reject) {
      self.$().fadeOut(500, resolve);
    });
  }
});

MyAlertModal = Ember.View.extend( FadingView, { /* ... */ });
          

Effects

...or CSS3 Transitions

Enter / LeaveStartDuring
Enter
.em-animate .em-enter
.em-enter-active
Leave
.em-animate .em-leave
.em-leave-active

Caveats and Gotchas

not quite production ready yet...

  • Container Destruction: Animations could interact with destroyed views
  • Animation Queues: Jump to the end of active animation when a second is triggered
  • Initial State: How should view / collection items that start in container be handled?

Caveats and Gotchas

not quite production ready yet...

  • Deferred destruction: Two views on the DOM at once, active bindings
  • Performance: Child view creation can cause major jitter in the animation
  • Same route, new data: Route's view is reused so no animation

Wrapping Up

  • Manipulating view lifecycle in a ContainerView can be easy...
  • ...but it means overwriting existing functions, copy pasta

  • Adding animation to your Ember app can be easy...
  • ...but animation as part of the Ember is not trivial
Robust view lifecycle with deferred support in Ember core → implement animation support with better building blocks

That's It