Re: [css-houdini-drafts] [css-animationworklet] Mechanism to pause/play an animation from inside worklet (#808)

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](https://user-images.githubusercontent.com/944639/55087806-0cf69280-5081-11e9-9235-c4d8f39997dc.png)




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)']})`

``` javascript

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
    }
  }
});
```
 



-- 
GitHub Notification of comment by majido
Please view or discuss this issue at https://github.com/w3c/css-houdini-drafts/issues/808#issuecomment-477212511 using your GitHub account

Received on Wednesday, 27 March 2019 15:34:24 UTC