Animating changes to data on a page


#1

Too many times I’ve needed to animate based on computed property changes and been foiled. My use case is usually the following:

  1. Computed property value gets set somewhere in a controller or component or updated via a model
  2. I want to animate the old value leaving the page
  3. I want to animate the new value entering the page

I’ve JSBin’d an example of a failed attempt to animate a CP change. The code is ugly, but it shows the approach I’ve tried to take in the past but given up on.

Sounds simple enough, but the problem that arises every single time is that there is a point at which both values need to be present at the same time. Take for example the following template:

Hello <span class='t-rotate t-rotate-in'>{{name}}</span> <button {{action 'updateProperty'}}>Update</button>

Imagine I have CSS classes t-rotate-in and t-rotate-out along with a transform transition that will rotate elements in and out by simply toggling classes.

The way I typically implement this currently is to insert the new element next to the existing element, and then remove the old element when it is fully animated out (by binding to the transitionEnd event).

Unfortunately, this technique falls down with Ember. When you print a value with handlebars, it creates a SimpleHandlbarsView instance in memory (and a serialized version in a “script” tag to allow looking up this view later). When the bound property value changes, the handlebars view merely calls jQuery’s html function to update the content inside the tag.

All this is to explain that we are never actually dealing with multiple instances of the containing tag, so my existing CSS transition approach doesn’t work here. I’m at a loss for how to implement these types of animations, but they can be very important to good UIs in large client-side apps. Has anyone else had success doing this with a different approach? Or do Ember views need more support for asynchrony before we can do this kind of thing?


#2

The best way to get this kind of behavior is to wrap it up in a component.

I fixed your JSBin to show how: http://jsbin.com/eFAXEKo/1/edit?js,output


#3

I’ve tried the same approach as you and have run into the same problem, which is, as you mentioned, that you need to have both values on the page at once in order to animate cleanly between them. When you try and solve this problem using observers in your controller, you are limited to toggling CSS classes. At this level you are fairly decoupled (and rightly so) from the view layer which is doing the actual rendering of data to the page when the render queue runs, so attempts to smoothly animate changes to data here will always fall short, especially considering the fact that you aren’t really supposed to know or care about when this queue runs.

One solution that I’ve had some good success with is to use ember-animated-outlet. This library essentially provides you with an Ember.ContainerView subclass called Ember.AnimatedContainerView.

Why this is useful: Normally Ember.ContainerView works like an array, but for views. As you add/remove items from a container view those changes are reflected to the DOM. An Ember.ContainerView has the ability to only display one child view at a time using its currentView property. This is how {{outlet}} works under the hood. An outlet is essentially an Ember.ContainerView that shows one view at a time based on the current state of your application. When an outlet’s currentView changes, it immediately destroys the old view and inserts the new one. This is why you can’t have both values on the page at once using a standard outlet.

Ember.AnimatedContainerView works just like Ember.ContainerView except when its currentView changes, instead of just swapping out the old view for the new one, it appends the new view to the page and gives you a hook to animate the transition. When the transition is done the old view is removed and destroyed.

I’ve updated your JSBin to show how you would do all of this programatically. Since you’re dealing with two divs now instead of just one, you’ll need to tweak the CSS classes slightly to achieve the exact same effect. But the main idea is there.