Adding fly-over animation from list to details
After I made list resorting animate I got excited about how much ember-animated can do with how little code. I was hooked and signed up to EmberMap's animation workshop at EmberConf. Having finished the workshop, my enthusiasm rose to new levels.
I decided to do another animation that you see in lots of apps. When an item is clicked in a list to go to its details page, it "flies over" to its new position on the details page (I wrote about how that's done with liquid-fire a long time ago on the AirPair blog).
Mark the pieces that animate
The first step is to tell ember-animated which value needs to be watched. If that value changes, the animation will be triggered. Here, the two components that render the player's image that I wanted to animate are player-card
and detailed-player-card
.
{{!-- app/templates/components/player-card.hbs --}}
<AnimatedContainer class="flex-1 w-full h-full">
{{#animated-value @player}}
<img
src={{@player.imageURL}}
alt={{@player.name}}
title={{@player.name}}
>
{{/animated-value}}
</AnimatedContainer>
{{!-- app/templates/components/detailed-player-card.hbs --}}
<AnimatedContainer>
{{#animated-value @player use=this.transition duration=500}}
<img
src={{@player.imageURL}}
alt={{@player.name}}
title={{@player.name}}
>
{{/animated-value}}
</AnimatedContainer>
The first parameter to animated-value
establishes identity. That's how ember-animated knows it should animate between animated-value
instances whose first params have the same value.
The use
parameter defines how the animation should be carried out, so let's take a look at that:
// app/components/detailed-player-card.js
// (...)
import move from 'ember-animated/motions/move';
import scale from 'ember-animated/motions/scale';
export default class DetailedPlayerCard extends Component {
// (...)
* transition(context) {
let { receivedSprites, sentSprites } = context;
for (let sprite of receivedSprites) {
move(sprite);
scale(sprite);
}
for (let sprite of sentSprites) {
scale(sprite);
move(sprite);
}
}
}
context
has three main properties on it that helps us, developers, implement the way we want objects to animate. sentSprites
are the objects that are present in the sender but not the receiver. receivedSprites
are the inverse – sprites appearing in the destination. The third one, keptSprites
, is not present in this example. It's the array of objects that are present both at the sender and the receiver. (The analogy to D3 joins can help comprehension.)
The transition method is defined in the detailed-player-card
component so when we go from the list to the details page (from player-card
to detailed-player-card
) we'll have a receivedSprite
. When we go the other way, from details to list, we'll have a sentSprite
.
To be able to animate the image (wrapped in animated-value
) back to the list even though its containing container (the detailed-player-card
component) have been destroyed, we also need to render an animated-orphans
component in a template that doesn't get destroyed between the transitions. This way, the animated-orphans
can adopt the parentless child.
I chose the application template:
{{!-- app/templates/application.hbs --}}
{{animated-orphans}}
{{!-- (...) --}}
With that I had a working fly-over animation:
The astute observer among you might notice two things:
- I fixed the jank from last time that was produced when the list is re-ordered. It wasn't a spectacular fix, I need to amend some flex-related CSS.
- When the image travels back from the details to the list page, it goes beyond the cards. I think that's not very bad but not ideal either and I might attempt a fix later.
If you have any questions, comments or observations, please write me at balint@balinterdi.com.