ember-animation / ember-animated

Core animation primitives for Ember.
https://ember-animation.github.io/ember-animated/
MIT License
244 stars 90 forks source link

Control when `AnimatedContainer` resize transition happens #218

Open emattias opened 4 years ago

emattias commented 4 years ago

For example: I would like to make the resize motion of AnimatedContainer to happen after the fadeOut in this example (this is a transition in a animated-value that is a direct child of an AnimatedContainer):

  transition = function* ({ insertedSprites, removedSprites }) {
    yield fadeOut(removedSprites[0], { duration: 150 });

    yield animatedContainerResize() // <- this is what I would like to do

    yield fadeIn(insertedSprites[0], { duration: 150 });
  }

If I have understood correctly you dont have control over when the resize motion happens for the AnimatedContainer.

Even by providing your own motion to the AnimatedContainer. I would be happy to be proven wrong! :)

chrism commented 4 years ago

Hi @emattias I have some good news! I think I can prove you wrong.

The key thing is that <AnimatedContainer> uses a motion, not a transition— which is slightly different and something I found a bit confusing. By default this motion is Resize.

But you can use something else.

For your example first create a new motion called dealyedResize, based on Resize, but which instead of resizing over the duration of the animation instead, waits for a third of the duration, then animates over the next third.

import { Resize } from 'ember-animated/motions/resize';
import { wait } from 'ember-animated';

class DelayedResize extends Resize {
  * animate() {
    yield wait(this.opts.duration / 3);
    this.opts.duration = this.opts.duration / 3;

    yield * super.animate();
  }
}

export default function delayedResize(sprite, opts) {
  return new DelayedResize(sprite, opts);
} 

And import it into whichever backing class the template uses as a property

import Controller from '@ember/controller';
import delayedResize from '../motions/delayed-resize';

export default class ApplicationController extends Controller {
  motion = delayedResize
}

So that you can reference it in the template instead of the default

Then add this motion to the <AnimatedContainer>

<AnimatedContainer @motion={{this.motion}}>

This should change the animation behaviour of the container already.

Then only thing left to do is to change the timing of your transition to match this, so that the transition is now

1/3: fade out 1/3: animate container (done via the delayedRezise on the container) 1/3: fade in

So the final backing class code would look like this with a motion for the container and a transition for the animated elements.

import Controller from '@ember/controller';
import { fadeIn, fadeOut } from 'ember-animated/motions/opacity';
import { wait } from 'ember-animated';
import delayedResize from '../motions/delayed-resize';

export default class ApplicationController extends Controller {
  *transition({ insertedSprites, removedSprites, duration }) {
    for (let sprite of removedSprites) {
      yield fadeOut(sprite, { duration: duration/3 });
    }

    yield wait(duration/3)

    for (let sprite of insertedSprites) {
      fadeIn(sprite, { duration: duration/3 });
    }
  }

  motion = delayedResize
}

This should achieve the effect you want I hope?

Full code example with working demo here https://github.com/chrism/ember-animated-container

emattias commented 4 years ago

Thanks alot for that answer! That looks to achieve what I asked for but in a, I would say, brittle way.

One reason for using js to orchestrate animations is tomorrow have to cobble them together with synchronised delays etc. So I have that we can add a way to have full control of the container resize motion without having to sync delays of animations.