w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.52k stars 672 forks source link

[view-transitions-2] Add method to get all animations of a ViewTransition object #9908

Open bramus opened 9 months ago

bramus commented 9 months ago

This is a side-issue to https://github.com/w3c/csswg-drafts/issues/9901 where this demo was mentioned.

To make this work, I basically need to hijack all animations and either pause them (when manually updating their currentTime) or give them non-DocumentTimelines (when attaching them to a ScrollTimeline).

The code to get all these animations is this:

const transition = document.startViewTransition(…);
await transition.ready;

const animations =
  document
    .getAnimations()
    .filter((anim) => {
      return anim.effect.target === document.documentElement && anim.effect.pseudoElement?.startsWith("::view-transition")
    });

While this works, it’s not very ergonomic. Therefore I’m suggesting something like ViewTransition.getAnimations() to easily get them:

const transition = document.startViewTransition(…);
await transition.ready;
const animations = transition.getAnimations();

This would also benefit Scoped Transitions, where you can have multiple View Transitions going on at once.

khushalsagar commented 5 months ago

document.documentElement.getAnimations would make this simpler and also work with scoped transitions. Does that suffice?

I'm ok with adding it to VT as well.

bramus commented 5 months ago

document.documentElement.getAnimations would make this simpler and also work with scoped transitions. Does that suffice?

This doesn’t work because the result of this doesn’t include the animations attached to the pseudos. For HTMLElement.geAnimations() to return anything usable here you’d need to get the animations from the subtree by calling document.documentElement.getAnimations({ subtree: true });.

However, that way you would get you all animations from the entire subtree and you’d to manually check things again.

vmpstr commented 5 months ago

Another approach would be something like document.documentElement.getAnimations({ pseudo: "::view-transition", subtree: true }); which would only get the subtree of the ::view-transition pseudo. That doesn't currently exist, but it's similar to getComputedStyle(element, pseudo)

bramus commented 5 months ago

I prefer ViewTransition#getAnimations() over document.documentElement.getAnimations({ pseudo: "::view-transition", subtree: true }); as with the former you can pass the ViewTransition object into any method and it can get the animations from it. With the latter you’d also need to pass in the element on which you triggered the VT.

(Which reminds me: we might also need a ViewTransition#getTransitionRoot() – or a readonly property that exposes it – once we have scoped transitions)

vmpstr commented 5 months ago

FWIW, I agree with ViewTransition.getAnimations(), it's just the Element.getAnimations() with a pseudo option is more general. I can't really think of any use cases for it though.

I agree that transition root is something that's useful. Would then viewtransition.getTransitionRoot().getAnimations({ pseudo: "::view-transition", subtree: true }) work? :)

bramus commented 5 months ago

Would then viewtransition.getTransitionRoot().getAnimations({ pseudo: "::view-transition", subtree: true }) work? :)

That could work indeed :)

khushalsagar commented 5 months ago

^ I like @vmpstr's suggestion above. So there's 2 independent proposals here:

  1. Add "pseudo" to the list of options for getAnimations.
  2. Add a getTransitionRoot() API to retrieve the element associated with the transition.

Should we defer on 2 until scoped transitions starts being spec'd? As it stands today this would always return the document element.

nt1m commented 5 months ago

+1 to @vmpstr's suggestion. Is the prior art pseudoElement or pseudo? Would be nice to be consistent if there's some prior art, otherwise pseudoElement is my preferred name.

bramus commented 5 months ago

The only one I can think of is getComputedStyle, whose 2nd argument is named pseudoElt.

ydaniv commented 5 months ago

Nit: shouldn't getting the root be a property? like viewTransition.transitionRoot? Kind of like getting Element.shadowRoot

ydaniv commented 5 months ago

Nit2: if we're extending Element.getAnimations(), how about adding a general selector property instead?

khushalsagar commented 5 months ago

how about adding a general selector property instead?

can you expand on that. I didn't follow. :)

bramus commented 5 months ago

Pinging @flackr as this steps into web-animations-1 territory. Start reading from this comment onwards.

ydaniv commented 5 months ago

how about adding a general selector property instead?

can you expand on that. I didn't follow. :)

Since we're setting subtree: true and we're getting animations from the entire subtree, use a selector to filter the elements from the subtree, instead of naming a single pseudo element. I think that could be handy in general.

khushalsagar commented 5 months ago

^ ah. I'm not opposed to that. But I favour adding a pseudo element option for consistency with other script APIs which similarly allow passing a pseudo element option.

As for naming, let's use the same keyword as element.animate : pseudoElement. Especially because all other API surfaces for animations use that.

flackr commented 5 months ago

it's just the Element.getAnimations() with a pseudo option is more general. I can't really think of any use cases for it though.

I could see this being useful to get the animations running on e.g. the ::before pseudo.

flackr commented 5 months ago

I'm in favour of @vmpstr's proposal. The pseudoElement attribute was added specifically for synergy with the getComputedStyle API for accessing pseudos so this further aligns the two APIs.

flackr commented 5 months ago

how about adding a general selector property instead?

can you expand on that. I didn't follow. :)

Since we're setting subtree: true and we're getting animations from the entire subtree, use a selector to filter the elements from the subtree, instead of naming a single pseudo element. I think that could be handy in general.

Adding a generic selector filter is a pretty substantial additional bit of complexity for which you can usually use the querySelectorAll API, e.g. elem.querySelectorAll(query).map(e => e.getAnimations()), where specifying the pseudo is just giving you a way to specify the actual node for your getAnimations call in a similar way to getComputedStyle, where we also don't have a generic selector.

css-meeting-bot commented 5 months ago

The CSS Working Group just discussed [view-transitions-2] Add method to get all animations of a ViewTransition object, and agreed to the following:

The full IRC log of that discussion <TabAtkins> vmpstr: there's some desire to get all the VT animations (the animations associated with the VT pseudos)
<TabAtkins> vmpstr: The proposal started with like adding .getAnimations() to the VT object
<TabAtkins> vmpstr: but the current proposal is to add a pseudo parameter to element.getAnimations()
<vmpstr> document.documentElement.getAnimations({ subtree: true, pseudoElement: "::view-transition" })
<TabAtkins> vmpstr: so that would mean get all the animations in the subtree, that are on the ::view-transition pseudo-element
<TabAtkins> vmpstr: yehonatan also had an idea for a selector filter for these
<TabAtkins> vmpstr: havne't thought thru that yet, but just adding the pseudo property as a first step makes sense
<TabAtkins> vmpstr: we'd also like to add a v-t-root property
<ydaniv> q+
<emilio> q+
<flackr> q+
<khush> q+
<TabAtkins> vmpstr: For now this is always the documentElement, but for scoped transitions it woudl be valuable. Lower priority for now.
<TabAtkins> vmpstr: So with this, you could get the transitioning root for a VT, then ask for all the animations from that element
<astearns> ack ydaniv
<TabAtkins> ydaniv: If we're adding a pseudo argument, i think the interaction with subtree is a bit funky
<TabAtkins> ydaniv: if it's just to get the animations of *that* pseudo-element, that makes sense on its own
<TabAtkins> ydaniv: but in this case you have an entire subtree under the pseudo, that's why you need subtree?
<TabAtkins> flackr: the pseudo is establishing the root node you're interacting with
<TabAtkins> flackr: so you could use it with subtree:false as well to get just from that pseudo
<TabAtkins> flackr: but with subtree:true it includes the subtree of that pseudo
<TabAtkins> ydaniv: It just seems weird to have somethign special just for a pseudo-element, which takes sort of a selector, and not have a more general api for a selector filter
<TabAtkins> flackr: all fo this is based on pseudo-elements not being properly implemented. element.animate() takes a pseudo, etc. We just don't have an APi to interact with pseudos right now.
<TabAtkins> ydaniv: okay, then no objection.
<flackr> q-
<TabAtkins> bramus: the gist is you're changing the root, not filtering
<astearns> ack emilio
<bramus> TabAtkins: (backs that up in more words)
<TabAtkins> TabAtkins: Right, you just *don't get the animations* on the pseudoes right now
<khush> q-
<TabAtkins> emilio: I think my objection was addressed by rob, wondered if it needed to work with subtree:false. but that makes sense now.
<TabAtkins> emilio: does anyone know offhand what getAnimations() does for ::before/after/etc with subtree:true?
<TabAtkins> flackr: they're included
<TabAtkins> vmpstr: Yeah, they'r eincluded, but since they're on the documentelement you end up getting *all* the animations on the whole page
<khush> q+
<TabAtkins> emilio: okay, that contradicted what Tab said at first
<TabAtkins> TabAtkins: yeah i was just wrong
<TabAtkins> khush: if you use a pseudo with element.animate(), and that could match multiple pseudos, what do we do with that?
<TabAtkins> khush: should we do the same behavior here?
<TabAtkins> flackr: I think you want it to mean all the ones that match
<bramus> TabAtkins: I dont remember what we did for element.animate but since we are querying here but not taking action it seems fine
<vmpstr> +1
<TabAtkins> seems fine to just return all of them when multiple pseudos match, that is
<TabAtkins> astearns: so summary?
<TabAtkins> vmpstr: Add a pseudoElement option to the element.getAnimations() dict arg
<TabAtkins> vmpstr: And subtree:true/false acts on that pseudo as the root
<TabAtkins> astearns: objections?
<TabAtkins> RESOLVED: Add a pseudoElement option to the element.getAnimations() dict arg. subtree:true/false acts on that pseudo as the root
<TabAtkins> astearns: should we resolve on the how to get the element from the transition object?
<TabAtkins> vmpstr: I don't think it's controversial, so if we could resolve that's great
<ydaniv> read-only property
<TabAtkins> vmpstr: new property on the VT object that's like .transitionRoot
<flackr> q+
<khush> +1 to read-only
<TabAtkins> vmpstr: read-only, returns the element that's hosting the VT
<astearns> ack khush
<astearns> ack flackr
<TabAtkins> flackr: might suggest .target, that's what animations use to say what they're animating
<TabAtkins> vmpstr: sounds fine to me
<TabAtkins> bramus: I'm a bit more of a fan of transition root, but we can talk about it later. I use the term "transition root" when speaking about it, "target" is a little more ambiguous of a term.
<TabAtkins> flackr: we can bikeshed the name. I'm good witha dding the accessor, dont' want to delay.
<bramus> s/but we can talk about it later. I /because when talking about I
<TabAtkins> astearns: so proposed resolution is to add a readonly .transitionRoot to the VT object
<TabAtkins> emilio: what type does it return?
<TabAtkins> vmpstr: an Element
<TabAtkins> TabAtkins: can you VT on a pseudo-element?
<TabAtkins> vmpstr: No, you ahve to invoke a JS method
<TabAtkins> emilio: sounds good
<TabAtkins> RESOLVED: Add .transitionRoot readonly property to VT object, reflectring the element hosting the VT.