Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-animationworklet] Mechanism to pause/play an animation from inside worklet #808

Open
majido opened this issue Aug 29, 2018 · 7 comments

Comments

Projects
None yet
2 participants
@majido
Copy link
Contributor

commented Aug 29, 2018

From @RByers on September 22, 2016 15:53

For example, in the spring example it should be possible for the Animator to somehow say "I'm done now, stop invoking my animate function".

Maybe it should be more powerful, eg. "sleep for 5 seconds"? Or a main-thread API to resume the Animator?

Copied from original issue: WICG/animation-worklet#8

@majido

This comment has been minimized.

Copy link
Contributor Author

commented Aug 29, 2018

From @appsforartists on September 27, 2016 21:21

pause implies that there's still potential work waiting to be done. at rest (or CSS transition's end) are better concepts for a spring that's run out of frames.

If functions were side-effect-free (#14) returning a falsey value would be a good way to represent the animation has come to rest.

@majido

This comment has been minimized.

Copy link
Contributor Author

commented Aug 29, 2018

From @flackr on April 18, 2017 6:15

Why does returning a falsey value require side-effect-free functions? My only concern with this model is how we know when to start invoking the animation again.

Alternate suggestion, could we allow pausing the input timelines: e.g.

static timelines() { return ['document']; }
animate(elementMap, timelines) {
  // do stuff
  if (done) {
    timelines[0].pause();
    // animate will no longer be called every frame, only when
    // some other input has changed at which point you can call
    // play() on the timeline to resume updating every frame.
  }
}
@majido

This comment has been minimized.

Copy link
Contributor Author

commented Aug 29, 2018

Pausing timeline is interesting though I wouldn't probably call it pause as it implies any other animation attached to the same timeline will also get paused. It is more like "detach/attach".

@majido

This comment has been minimized.

Copy link
Contributor Author

commented Aug 29, 2018

From @flackr on April 18, 2017 8:0

I think each instance has its own timelines, since the registered timeline list only specifies how to construct the timelines but doesn't contain actual constructed timelines.

Another idea I had was to create a mutable timeline list that animate can add to / remove from. An animator could then remove the document timeline and readd when it was needed again.
e.g.

animate(elementMap, timelines) {
  // do stuff
  if (done) {
    timelines.pop();
    // animate will no longer be called every frame, only when
    // some other input has changed.
  } else if (timelines.length == 0) {
    timelines.push(new DocumentTimeline());
  }
}

@majido majido changed the title Mechanism to pause an animation [css-animationworklet] Mechanism to pause an animation Aug 29, 2018

@majido majido changed the title [css-animationworklet] Mechanism to pause an animation [css-animationworklet] Mechanism to pause/play an animation from inside worklet Mar 12, 2019

@majido

This comment has been minimized.

Copy link
Contributor Author

commented Mar 27, 2019

After some recent discussion with @flackr and @stephenmcgruer , we think this may be a key feature to enable animation effects that are driven both by input events and time.

Consider a simple swipe-to-dismiss effect, which follows the user swipe gesture and when finger lifts then continues to completion (e.g., dismiss or return to original) with a curve that matches the swipe gesture's velocity.

With Animation Worklet, this can be modeled as a stateful animation which consumes both time and pointer events and have the following state machines:

SwipeToCompletionAnimation

Here are the three main states:

  1. Animation is idle, where it is paused so that it is not actively ticking
  2. As soon as the user touches down, the animation moves the target to follow the user touchpoint while staying paused (optionally calculate the movement velocity, and overall delta).
  3. As soon as the user lift their finger the animation will the switch to 'playing' so that it is ticked by time until it reaches its finished state. The final state may be decided on overall delta and velocity and the animation curve adapts to the movement velocity.

Note that while in (3), if the user touches down we go back to (2) which ensures responsiveness to user touch input.

To make this more concrete, here is something like this can be coded assuming we have the proposed APIs for pause/play from worklet and also receiving input events. Note that all the state machine transitions and various state data (velocity, phase) and internal to the animator. Main thread only needs to provide appropriate keyframes that can used to translate the element on the scroll as appropriate (e.g., Keyframes(target, {transform: ['translateX(-100vw)', 'translateX(100vw)']})

registerAnimator('swipe-to-dismiss', class SwipeAnimator extends StatefulAnimator {
  constructor(options, state = {velocity:0, phase: 'idle'}) {
    this.velocity = state.velocity;
    this.phase = state.phase;

    if (phase == 'idle') {
      // pause until we receive pointer events.
      this.pause();
    }

    // Assume we have an API to receive pointer events for our target.
    this.addEventListener("eventtargetadded", (event) => {
     for (type of ["pointerdown", "pointermove", "pointerup"])  {
        event.target.addEventListener(type,onPointerEvent );
     }
    });
  }

  onpointerevent(event) {
    if (event.type == "pointerdown" || event.type == "pointermove") {
      this.phase = "follow_pointer";
    } else {
      this.phase = "animate_to_completion";
      // Also decide what is the completion phase (e.g., hide or show)
    }

    this.pointer_position = event.screenX;

    // Allow the animation to play for *one* frame to react to the pointer event.
    this.play();
  }

  animate(currentTime, effect) {
    if (this.phase == "follow_pointer") {
      effect.localTime = position_curve(this.pointer_position);
      update_velocity(currentTime, this.pointer_position);
      // Pause, no need to produce frames until next pointer event
      this.pause();
    } else if (this.phase = "animate_to_completion") {
      effect.localTime = time_curve(currentTime, velocity);

      if (effect.localTime == 0 || effect.localTime == effect.duration) {
        // The animation is complete. Pause and become idle until next user interaction.
        this.phase = "idle";
        this.pause();
      } else {
        // Continue producing frames based on time until we complete or the user interacts again.
        this.play();
      }
    }


  }

  position_curve(x) {
    // map finger position to local time so we follow user's touch.
  }

  time_curve(time, velocity) {
    // Map current time delta and given movement velocity to appropriate local time so that over 
    // time we animate to a final position.
  }

  update_velocity(time, x) {
    this.velocity = (x - last_x) / (time - last_time);
    this.last_time = time;
    this.last_x = x;
  }

  state() {
    return {
      phase: this.phase,
      velocity: this.velocity
    }
  }
});
@majido

This comment has been minimized.

Copy link
Contributor Author

commented Mar 27, 2019

BTW this is an example of swipe-to-dismiss/action effect that I am referring to https://twitter.com/kzzzf/status/917444054887124992

@birtles

This comment has been minimized.

Copy link

commented Apr 1, 2019

Sounds like a great use case to try prototyping. The example from twitter appears to have four states (animating to origin vs animating to completion) and there may be other subtleties that help inform the API (e.g. factoring in the position of a second pointer down event).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.