csswg-drafts icon indicating copy to clipboard operation
csswg-drafts copied to clipboard

[css-shared-element-transitions-1] Define "active animation" used to signal end of transition

Open khushalsagar opened this issue 2 years ago • 25 comments

Shared Element Transition creates an ephemeral tree of elements with snapshots from incoming and outgoing DOM state. We need to define the lifetime of this tree. Since the purpose of this tree is to permit animations on these elements, we rely on the existence of the animations to decide how long this tree lives.

For declarative animations (CSS transitions, CSS animations and Web Animations) which can be detected by the browser, the [spec] (https://drafts.csswg.org/css-shared-element-transitions-1/#issue-9b6c2555) talks about detecting when any of the generating elements have active animations. We need to define what active means here.

Each of the animation APIs above provide an event that the developer can use to detect if the animation was finished or cancelled:

We want to ensure that these events are fired before assuming that an element has no active animation. This allows the developer to chain animations by waiting for one to finish before triggering the next. The precise proposal is as follows:

  • Immediately after the transition elements are generated a ready promise is resolved to notify the developer that these elements can be targeted in script. Wait until this event is fired.
  • Then check that none of the generated elements have an animation which is waiting to start, is currently running or is pending dispatch of finished or cancel events. Since checking if an element has an animation waiting to start requires a clean style the check may be deferred until the next rendering opportunity if the style is dirty. Similarly this check runs after dispatch of the finished/cancel animation events.
  • Once there is a check when none of the elements have an animation in the state above, the transition is assumed to be finished and generated elements are removed.

I'm not sure if the above can be simplified by running the check at the end of every update the rendering loop. That loop might not be triggered after the animationend event (if there are no new updates to draw?). Ideas welcome on that.

Note: This doesn't cover script driven animations (like the ones that use rAF). We'll need a separate script API for that. This is targeted for the use-case where the developer is using declarative animations and the transition elements automatically go away once these animations are done.

khushalsagar avatar Sep 23 '22 19:09 khushalsagar

Each of the animation APIs above provide an event that the developer can use to detect if the animation was finished or cancelled:

CSS animations and CSS transitions are built on top of Web Animations so all of these animations will produce a corresponding Animation and all animation events should use the common pending animation event queue.

It should be enough to wait until there are no Animations in the running state and the pending animation event queue is empty (and no new animations have been generated as a result). (The running state includes animations that have yet to start due to being in their delay phase but excludes animations that are paused/idle/finished.)

The processing of the pending animation event queue is handled in the update animations and send events procedure which is referenced as part of update the rendering.

Bear in mind that animations can be triggered to repeat indefinitely and you might need to determine if they should be included in scope or not. Also, presumably you'll want to exclude scroll-based animations from this definition too.

Also, there are SMIL/SVG animations which ultimately were intended to be implemented in terms of Web Animations but we never got around to it. These can also trigger new animations when they finish in a declarative manner (syncbase timing and event-based timing).

birtles avatar Sep 24 '22 02:09 birtles

I think we should do it by:

  1. While true:
    1. Let animations be all the not-finished animations targeting the transition pseudo-elements
    2. If there are none, the transition is over. Return.
    3. Wait for all animations to finish.
    4. Wait some kind of task/microtask to allow animations to be chained via those animations' finish event & finished promise.

For animations that don't use CSS/web animations, we'll add some other API for that later.

I haven't looked into the detail for 1.4, but I seem to remember the animations spec does something odd with the event loop here.

jakearchibald avatar Sep 24 '22 13:09 jakearchibald

You'll probably want to ignore paused and idle animations too? Certainly idle ones.

I can't recall anything particularly odd with regards to the event loop.

birtles avatar Sep 24 '22 13:09 birtles

You'll probably want to ignore paused and idle animations too? Certainly idle ones.

I think it's ok for the transition to be infinite if the developer has deliberately paused the animation. It might be useful in debugging for instance.

I can't recall anything particularly odd with regards to the event loop.

The animations spec performs an additional microtask checkpoint here https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events. I think that results in particular promises resolving before their associated event fires.

jakearchibald avatar Sep 24 '22 14:09 jakearchibald

I think it's ok for the transition to be infinite if the developer has deliberately paused the animation.

+1. I assumed that the developer may also pause the animation for a particular effect. Infinite animations fall in the same category. We expect the developer will remove them when the intended effect is finished.

But idle animations is a good point. We'll need to wait for the step which processes all new animations after it has been set up (either in CSS or JS) to ensure it changes from idle to running state. For example if its start time is unresolved. Does that happen as a part of update-the-rendering loop? After that can an animation still be in idle state?

presumably you'll want to exclude scroll-based animations from this definition too

+1.

For animations that don't use CSS/web animations, we'll add some other API for that later.

+1 to this as well. We will have a script based API that the developer can use to signal when their animations are done. Technically we could just have that API and the developer can listen to animationfinish events to signal when they're done. So any animations we don't handle automatically will have that fallback. The reason we're doing the automatic detection for this class of declarative animations is developer convenience for common animations APIs on the platform.

khushalsagar avatar Sep 26 '22 18:09 khushalsagar

IMHO, pausing an animation is mostly interesting if progress is driven by user input, i.e. a gesture, but since you intend on blocking that in #7797, being able to pause a transition seem more like a foot-gun here. And given the above, plus excluding scroll-linked animations, I suppose we probably want to ignore any value of animation-timeline that's not auto for now?

ydaniv avatar Sep 28 '22 10:09 ydaniv

But idle animations is a good point. We'll need to wait for the step which processes all new animations after it has been set up (either in CSS or JS) to ensure it changes from idle to running state. For example if its start time is unresolved. Does that happen as a part of update-the-rendering loop? After that can an animation still be in idle state?

Animations that have pending play or pause task are not reported as idle so they will still be idle after the updating the rendering. Idle animations are those which have never been scheduled to play (e.g. just created with the Animator ctor without ever setting the start time or calling play()) or which have been cancelled for some reason.

If the start time is unresolved because the the author has called play() (or the CSS bindings have caused it to be played) but the browser has yet to determine the start time (which it typically resolves after painting the assets for the first frame), then it will still be reported as running, not idle, because it has a pending play task.

birtles avatar Sep 28 '22 12:09 birtles

@ydaniv The input blocking referenced in #7797 is only for the duration when rendering is suppressed. Which starts one frame after createTransition (that is cached by the browser) and ends when the promise returned by updateDOM callback resolves. The duration while the animations run don't block input.

Thanks for clarifying @birtles. The behaviour for the following states can then be:

  • idle: Ignored. If the animation is waiting to start at the next update the rendering loop it'll have a pending play task so will be considered running.
  • paused: Unclear whether this should be considered active.
  • finished: Ignored. If we're still waiting on dispatch of finish events we can rely on the pending animation event queue being empty.
  • running: Considered active.

For the animation-timeline suggestion, it does make sense to exclude scroll-timeline but I'm not sure if we should exclude everything except document timeline. @flackr on this too.

khushalsagar avatar Sep 28 '22 16:09 khushalsagar

For the animation-timeline suggestion, it does make sense to exclude scroll-timeline but I'm not sure if we should exclude everything except document timeline. @flackr on this too.

Regarding excluding scroll timeline, wasn't swipe animations into other documents one possible use case for this API? If so, you probably do want to wait for the swipe into the other document to be finished, right? There are of course many complexities we'd have to figure out for how to set up such an animation but it seems like a valid use case.

flackr avatar Sep 28 '22 16:09 flackr

I'm not sure how gesture based transitions can be built using scroll timeline yet. The swipe gesture imitated by the user would be a trigger to initiate the transition itself. So the developer would have to issue document.createTransition in response to the gesture starting. Input then gets discarded for the capture and update phase.

Once the animations are ready to start you somehow set up the container pseudo-element (which has the old and new root images) as a scroll container to provide a scroll-timeline for animating the old and new images..? Because this is still unclear I figured it's better to exclude scroll-timeline.

If we are including it, what happens if there is no user gesture. Does the associated animation stay in running state?

khushalsagar avatar Sep 28 '22 16:09 khushalsagar

Thanks @khushalsagar for clarifying! I missed that.

I'm not sure how gesture based transitions can be built using scroll timeline yet. The swipe gesture imitated by the user would be a trigger to initiate the transition itself. So the developer would have to issue document.createTransition in response to the gesture starting. Input then gets discarded for the capture and update phase.

Exactly, that's why current spec feels very awkward in conjunction with a pointer/scroll-linked timeline.

If I really had to be creative, I guess maybe something like: User reached end of scrolling container (or list with a mandatory scroll-snap &c.) and at that point the author initiates a transition, which is later only progressed if the user continues scrolling on same direction.

Once the animations are ready to start you somehow set up the container pseudo-element (which has the old and new root images) as a scroll container to provide a scroll-timeline for animating the old and new images..?

Interesting! But then, what would the duration of the timeline be? Would the author need to define some sort of a "virtual" length?

Because this is still unclear I figured it's better to exclude scroll-timeline.

Yep, there are still a lot of open questions here, but then I think it's also worth defining explicitly what happens if the author did set animation-timeline to anything other than auto, in a way that's also future compat.

paused: Unclear whether this should be considered active.

This may still be useful for allowing authors to still be able to do custom stuff, and perhaps patterns can emerge there.

ydaniv avatar Sep 29 '22 07:09 ydaniv

which is later only progressed if the user continues scrolling on same direction.

These types of transitions are generally set up to commit to either the new or current state based on how far the user scrolls. So you want to keep track if the user continues scrolling and what the offset is when the gesture ends. Another complexity which I was unsure about was, do we need to run another hit test and lock a new scroll target since the scrolling container which initiated the transition could go away as a part of the DOM update?

what would the duration of the timeline be? Would the author need to define some sort of a "virtual" length?

I assumed it could use scroll-timeline but haven't thought through the details.

what happens if the author did set animation-timeline to anything other than auto, in a way that's also future compat.

I'm assuming animations using scroll-timeline and view-timeline are ignored if there is no user interaction. Would that already be the case with the definition above that only considers an animation active if its in running or paused state? I don't know what state these are in if there isn't an active scroll gesture.

If the above is reasonable then I'd be ok with not special-casing animations using a different timeline.

This may still be useful for allowing authors to still be able to do custom stuff

+1

khushalsagar avatar Sep 29 '22 19:09 khushalsagar

Concluding the discussion so far. The transition reaches a state with no active animations when the following is true:

  1. None of the transition pseudo-elements have an animation in paused or running state.
  2. There are no events in pending animation event queue associated with an animation on the transition pseudo-elements. I think this addresses @jakearchibald's here since clearing the queue happens after microtask checkpoint.

I also propose we limit the animations considered in 1 to the ones using the Document timeline for now. For scroll-timeline for instance, looks like the animation will remain in running state as long as the scrollable element has overflow to scroll. Seems like this will become a foot-gun. We can start with treating these like rAF driven animations, where the developer decides when to consider them inactive. Based on the usage patterns we see, we can later add them to this automatic logic.

khushalsagar avatar Oct 05 '22 17:10 khushalsagar

I also propose we limit the animations considered in 1 to the ones using the Document timeline for now.

We'd need to clarify if this means animations associated with the default document timeline or any document timeline associated with the current document.

(It would be more generic to refer to animations associated with a monotonically increasing timeline but then that raises the question of the scope of timelines considered. For Document.getAnimations() it actually works backwards from the elements in the document, to the effects, to the animations so that it covers animations associated with a timeline from another document.)

birtles avatar Oct 05 '22 23:10 birtles

The CSS Working Group just discussed [css-shared-element-transitions-1] Define "active animation" used to signal end of transition, and agreed to the following:

  • RESOLVED: only consider animations using document timeline for determining when the end of a view transition has come
  • RESOLVED: View animations are still active if there are running or paused view transition animations or any events in the pending event queue associated with animations on these pseudos
The full IRC log of that discussion <dael> Topic: [css-shared-element-transitions-1] Define "active animation" used to signal end of transition
<fantasai> khush, I think wrt group, since in e.g. SVG and vector editing, groups are a hierarchical concept
<dael> github: https://github.com/w3c/csswg-drafts/issues/7785
<fantasai> khush, I think it's better not to use it for the image-pair-wrapper
<dael> khush: The bg for this issue is that the pseudo elements we just discussed generated for a transition are meant to be temporary. need to decide when it's torn down
<fantasai> khush, seems more appropriate for the higher-level construct that you can nest other things inside
<TabAtkins> q+
<dael> khush: We use declarative elments to decide. web animations, animations, transitions. raff -based are excluded b/c browser doesn't know when we're done. For these want to auto-detect done
<dael> khush: Wanted precise def for what state animations are in for active
<TabAtkins> Actually kind of a point of order for this
<dael> khush: Issue leaned to rely on animation state. Go through all pseudo elements and check for idle, running, paused
<dael> TabAtkins: The entire point of the issue I wrote is it's supposed to abort if any other shared element transition are running, not any other animation. I never intended for web or css animations to be part of it. When I wrote spec I presumed only 1 running shared element transition at a time and need to check.
<dael> khush: I think might be confusing different part of feature. This is have a single transistion going and need to know when it ends. That relies on the pseudo elements generated for the transition
<dael> TabAtkins: Possibly. I don't recall writing that but okay
<TabAtkins> Okay yes, I was misreading
<TabAtkins> Ignore me. 😅
<astearns> ack TabAtkins
<dael> khush: Clarify exact point. Started a transtion and have detected shared elements. Constructed full pseudo element tree. UA animations applied. Nw want to detect when tree can be torn down. We keep checking animastions on all pseudo elements. Need to define when animation is active for this transition
<dael> khush: I see IRC. Glad on same page
<dael> khush: On issue, concultion is check for any aniamation in running or paused. If yes, it's still acitve. Then check pending animation event queue. If there's something in there the's a pending event. Use case for that is chaining and we want to make sure all events chained
<dael> khush: Hoping to resolve on that
<dael> khush: Second is animation timeline. Animation uses doc timeline and we know timeline will progress. If you aheva scroll timeline the timeline progress dpends on if there's a scroll gesture.
<vmpstr> q+
<astearns> ack vmpstr
<dael> khush: Prop is ignore those for now b/c couldn't narrow down. When we decide a solution for raff we can handle similar or decide when have more idea of use
<dael> vmpstr: Even with doc timeline you can set up animation that loops so can get same situation
<dael> khush: True, but in that case it is easier to tell. With scroll timeline it's ambig since user gesture might never happen
<dael> astearns: Sounded like 2 things to resolve. Timeline first?
<dael> khush: Sure. Prop: Only consider animations using the document timeline in the active animation set
<dael> astearns: Opinions? Questions?
<dael> astearns: as I understand, only consider animations using doc timeline for determining when the end of a view transition has come
<dael> astearns: Concerns?
<dael> astearns: Obj?
<dael> RESOLVED: only consider animations using document timeline for determining when the end of a view transition has come
<ydaniv> only paused and running too, right?
<dael> astearns: Second
<dael> khush: Second For the animations we consider we consider them active if there's in running or paused and when there are not animations in the pending event queue
<dael> astearns: Can be animations on things other than view transform pseudos
<dael> khush: right.
<dael> khush: No animations on the pending event queue that correspond to these pseudos
<ydaniv> that's cool
<dael> astearns: Prop: View animations are still active if there are running or paused animations in the pending event queue associated with these pseudos
<dael> astearns: I imagine I may have something wrong
<dael> khush: View animations are still acitve if they are in runing or paused state and if there are any events in the pending event queue associated with them
<ydaniv> * View transition animations
<khush> View animations are still active if there are running or paused animations or any events in the pending event queue associated with animations on these pseudos
<dael> astearns: More discussion on this?
<dael> astearns: Objections?
<dael> astearns: One ammendment. View animations are still acitve if there are running or paused animations
<dael> astearns: One ammendment. View animations are still acitve if there are running or paused view transistion animations
<dael> khush: View animations means animations on any of thses pseudo elements
<dael> astearns: Obj?
<dael> RESOLVED: View animations are still active if there are running or paused view transition animations or any events in the pending event queue associated with animations on these pseudos
<dael> astearns: I see a comment in github while we discussed. He's asking about timelines to current document.
<dael> astearns: Is the comment something we can decide on or should we go back to the issue?
<astearns> https://github.com/w3c/csswg-drafts/issues/7785#issuecomment-1269109950
<dael> astearns: Comment ^
<dael> khush: I need to read on this more. Okay coming back for this

css-meeting-bot avatar Oct 05 '22 23:10 css-meeting-bot

Thanks for the question @birtles. Can you give an example of a document timeline (other than the default document timeline) which is associated with the a document. I couldn't figure out how these would be created/used from the spec text regarding timelines associated with a document.

I want to avoid including timelines which are not guaranteed to move forward at frame boundaries. They could rely on events which may not occur and that makes it unclear how the animation will finish. That's not the issue with the default document timeline based on the text here: "since the time values of the default document timeline have a zero offset from the time origin, document.timeline.currentTime will roughly correspond to Performance.now() [HR-TIME]".

But I'm not sure if the above is true for other document timelines or the generic monotonically increasing timelines. While its clear that these timelines will always move forward, I'm not sure what triggers their advancement.

khushalsagar avatar Oct 06 '22 18:10 khushalsagar

Thanks for the question @birtles. Can you give an example of a document timeline (other than the default document timeline) which is associated with the a document.

Sure, new DocumentTimeline().

I couldn't figure out how these would be created/used from the spec text regarding timelines associated with a document.

You can use it as follows:

const timeline = new DocumentTimeline(performance.now());
const animation = new Animation(new KeyframeEffect(elem, { opacity: 0 }, 1000), timeline);

Alternatively you can take an existing animation and set its timeline using the timeline setter on Animation.

It's usage is probably almost non-existent but from a spec point of view I think it probably makes sense to include non-default document timelines associated with the current document.

I want to avoid including timelines which are not guaranteed to move forward at frame boundaries. They could rely on events which may not occur and that makes it unclear how the animation will finish. That's not the issue with the default document timeline based on the text here ...

Right. The same is true for non-default document timelines.

But I'm not sure if the above is true for other document timelines or the generic monotonically increasing timelines. While its clear that these timelines will always move forward, I'm not sure what triggers their advancement.

It is for other document timelines. No other types of monotonically increasing timelines currently exist so it's hard to say if that guarantee will hold so perhaps it's safest to limit it to document timelines.

birtles avatar Oct 07 '22 00:10 birtles

Thanks for that example! Agreed on including all Document timelines associated with the Document but excluding monotonically increasing timelines.

I've sent you a PR here to make sure the spec text is accurate.

khushalsagar avatar Oct 07 '22 18:10 khushalsagar

but excluding monotonically increasing timelines.

I'm confused, I think you mean: "execlude non monotonically increasing timelines", right?

Also, with regard to that, the whole idea of paused animations is that the author may drive these using rAF to any direction, including backwards. The main difference I guess is that these are still being controlled by the author, as opposed to, e.g. scroll-linked ones, that are controlled entirely by the user.

Does this defeat the idea of including paused animations?

ydaniv avatar Oct 08 '22 09:10 ydaniv

"execlude non monotonically increasing timelines", right?

Sorry I meant to say "Agreed on excluding all timelines other than Document timelines".

The main difference I guess is that these are still being controlled by the author, as opposed to, e.g. scroll-linked ones, that are controlled entirely by the user.

That's the main rationale for excluding them, the scroll-linked ones rely on the user. Technically you could have a JS driven scroll but I suspect that's not the main use-case and it'll end up being a footgun. Pausing document timeline animations don't have this issue. Am I missing something there?

khushalsagar avatar Oct 11 '22 18:10 khushalsagar

Technically you could have a JS driven scroll but I suspect that's not the main use-case and it'll end up being a footgun

I mean simple WA API animations, not scroll-linked ones, that are paused, and then driven by the author - say via user interaction (e.g. touchmove). So these too can have their currentTime set back and forth. My question is do we count these as monotonically increasing as well? Or in a more generic form, do we disallow scrubbed effects entirely? Or allow them for now only via the mechanism I described above?

ydaniv avatar Oct 12 '22 17:10 ydaniv

I assumed limiting to Animations which must use an animation-timeline of type document timeline implies their currentTime can only move forward. Is that not the case? Maybe an example of a use-case which should work but won't with the limitation to document-timeline would help.

Btw a use-case for paused animations that I had in mind relates to network events. To avoid the latency of loading images in starting the transition. For instance an author can start the transform/size animations to move an element to its position in the new DOM but pause the cross-fade until the new image loads (or there is a timeout).

let fadein = document.documentElement.animate(...,
    {
      pseudoElement: "::page-transition-incoming-image(thumbnail)",
    }
  );

let fadeout = document.documentElement.animate(...,
    {
      pseudoElement: "::page-transition-outgoing-image(thumbnail)",
    }
  );

fadein.pause();
fadeout.pause();
startOnThumbnailLoadOrTimeout(fadein, fadeout);

khushalsagar avatar Oct 13 '22 19:10 khushalsagar

OK, I guess I was confusing a bit between Animation.currentTime and Timeline.currentTime, but still, you can see what I mean in the following example: https://codepen.io/ydaniv/pen/PoeLbqZ I create an animation with timeline = document.timeline. The animation is paused, and I can scrub it via user interactions, in this example on scroll events, but doesn't have to be.

So the question is whether we're ok with this pattern?

Btw a use-case for paused animations that I had in mind relates to network events

Yes, exactly. This is a typical use-case for holding an animation, and of course, after simple gesture like a swipe or a click, and then assuming the developer simply calls .play().

ydaniv avatar Oct 14 '22 21:10 ydaniv

So the question is whether we're ok with this pattern?

I think the short answer is yes. It's also extremely useful to support paused animations for debugging the animations in developer tools.

Per the working group resolution and the updated spec text the plan is to fully support paused animations.

flackr avatar Oct 14 '22 23:10 flackr

Thanks for that example @ydaniv. Didn't realize you could have the animation's currentTime be disjoint from the associated timeline this way!

+1 to flackr's comment. Paused animations should be supported for multiple use-cases and that implicitly also allows scrubed animations with the pattern you mentioned.

khushalsagar avatar Oct 17 '22 17:10 khushalsagar

The spec edit for this has landed.

khushalsagar avatar Nov 07 '22 20:11 khushalsagar