-
Notifications
You must be signed in to change notification settings - Fork 143
[css-animationworklet] Mechanism to pause/play an animation from inside worklet #808
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
Comments
From @appsforartists on September 27, 2016 21:21
If functions were side-effect-free (#14) |
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.
}
} |
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". |
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. 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());
}
} |
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: Here are the three main states:
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., 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
}
}
}); |
BTW this is an example of swipe-to-dismiss/action effect that I am referring to https://twitter.com/kzzzf/status/917444054887124992 |
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). |
@majido What's the distinction between From my current hacking around, even animations that are told to run for IMO the swipe-to-dismiss example demonstrates a great API to have available. |
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
The text was updated successfully, but these errors were encountered: