Hello everybody!
The core team has wished for built-in animation support as a feature in Ember 1.1. This is a proposal for the public API.
Scope
Only route transitions. No list view animation for now.
Enabling animated transitions on an outlet
All you have do is define a transition effect on your outlet.
{{outlet transition="slideLeft"}}
There is no special {{animatedOutlet}}
helper and no this.transitionToAnimated()
like Ember Animated Outlet had. It’s all built-in and triggered by having transition
set to a non-empty value.
Handling “back-transitions”
When a user hits the browser’s back-button we typically want to handle this in a different way. If an outlet has a slideLeft
effect, we want to perform a slideRight
effect when navigating back in the browser’s history stack. The “back-transition” effect should also be configurable on the outlet:
{{outlet transition="slideDown" backTransition="slideRight"}}
If you want to trigger the equivalent as the user hitting the browser’s back-button you can use the {{linkBack}}
helper, which works just like {{linkTo}}
except that you can’t specify a route path. {{linkBack}}
will always take you one step back in the browser’s history stack. Example:
{{#linkBack}}Go back{{/linkBack}}
If you programmatically want to trigger a back-transition from a controller/route you can call this.transitionBack()
. Example:
App.PostEditController = Ember.ObjectController.extend({
save: function() {
var self = this;
this.get('model').commit().then(function() {
self.transitionBack();
//This will take you back to whichever route triggered the edit, which could be either `post.index` (show) or `posts.index` (list). The `backTransition` effect will automatically be used
});
}
});
The transitionBack
method delegates to the Ember.Location
instance being used by the router. Ember.HistoryLocation
and Ember.HashLocation
instances will delegate to window.history.go(-1)
, while Ember.NoneLocation
will have to store a URL history stack itself (we don’t want to mess with window.history
when running integration tests for example).
Transition flow
- As soon as a transition starts animating it can’t be stopped. Any following transitions (both animated and non-animated) will be queued until the transition animation is done. The queue can only hold one transition. Consecutive transitions will cancel pending transitions (which is the same case with async transitions). Transition effects should be fast, so we don’t block the user’s workflow.
- A transition should never animate more than one outlet (the topmost outlet that changes it’s contents in the transition).
Registering your own effects
Ember should ship with its own standard effects. Examples: slideLeft
, slideRight
, slideUp
, slideDown
, flipLeft
, flipRight
etc.
But it should also be easy to add your own effects. This can be done this way:
Ember.ContainerView.registerTransition('slideOverLeft', function(ct, oldView, newView, done) {
//Perform animation and invoke `done` when completed
});
You can tell Ember about back-transitions:
Ember.ContainerView.registerTransition('slideOverRight', function(ct, oldView, newView, done) {
//...
});
Ember.ContainerView.registerBackTransition('slideOverLeft', 'slideOverRight');
This way you can do {{animation transition="slideOverLeft"}}
and Ember will automatically know to perform slideOverRight
when the user clicks the back-button.
Animation implementation
The animations should definitely use CSS transitions where supported with a fallback to old school JS animation.
I believe the best way to implement the transitions is to utilize an external micro-library built to programmatically handle CSS transitions. The best one I know of is jQuery.transit. It will add an extra dependency, but only if you’re going to use animation.
The (bad) alternative would be to build a lot of css transition handling into Ember with fallback to jQuery.animate
. But there is no sense in adding this big a maintenance burden.
Issues from Ember Animated Outlet that will be fixed:
- Views that are animated will be frozen (i.e. no DOM changes can occur). This will avoid the issue where an outgoing view is changed since it’s controller changes its content. It will also improve FPS especially on mobile (source: @krisselden).
- Animated transitions to the same route with a different model will work. It does not work today since the same view is reused = nothing to animate out.
- Correct back-button handling.
- Child views no longer need to be explicitly defined so they get an element and are not just metamorphs.
Known limitations
- Since the transition effect is set directly on the outlet it is not possible to manually override which effect to use for a specific transition. You can somewhat achieve this by using a binding for the
transition
property on the outlet:{{outlet transition=myTransitionProperty}}
. - It’s a limitation (that we probably want to look at at a later point) that animations are queued. With some effects, such as slide effects, it would actually be possible to animate multiple transitions at the same time. Think of a photo album where the user can click Next and Previous in the bottom. But there are a number of issues with this that we need to solve:
- When a transition is midway and the user hits the back button the router needs to reuse the exact same view so we don’t duplicate the first view’s content.
- The animation effect code has to take multiple views into account (not just
oldView
andnewView
). - Some effects are not compatible with each other meaning that they can’t be performed at the same time (e.g. a
slideLeft
and aflip
).
Give me all the feedback!