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-view-transitions-2] Creating 'classes' of transition groups #8319

Closed
jakearchibald opened this issue Jan 17, 2023 · 88 comments · Fixed by #9773
Closed

[css-view-transitions-2] Creating 'classes' of transition groups #8319

jakearchibald opened this issue Jan 17, 2023 · 88 comments · Fixed by #9773
Labels
css-view-transitions-2 View Transitions; New feature requests

Comments

@jakearchibald
Copy link
Contributor

jakearchibald commented Jan 17, 2023

https://codepen.io/jaffathecake/pen/VwBprqL

In this example there are lots of boxes. Each has a unique view-transition-name, but each animates in the same way.

In the demo, I'm able to use selectors like ::view-transition-group(*) to target all of them, but that's only because they're the only thing animating. That's unlikely to be the case in the real world.

It'd be nice to have a way to style the animation of all of the boxes at once.

Some ideas:


.box-1 {
  view-transition-name: box-1;
  view-transition-class: box;
}

.box-2 {
  view-transition-name: box-2;
  view-transition-class: box;
}

::view-transition-group(*, box) {
  /* … */
}

The pseudo-class selector is ::view-transition-group(view-transition-name, view-transition-class). Unlike view-transition-name, many elements can be given the same view-transition-class.

This style of selector would work on the other pseudo-elements too, such as ::view-transition-new(view-transition-name, view-transition-class).


::view-transition-group(*):element-class(box) {
  /* … */
}

(I can't think of a good name for this. I know the current name is bad)

:element-class(ident) matches a view transition pseudo, if either the associated outgoing or incoming element has/had a class of ident.

We could also have :old-element-class(ident) and :new-element-class(ident) to work with classes specifically on the outgoing or incoming element.

@jakearchibald
Copy link
Contributor Author

With the first proposal, what happens if view-transition-class changes between the old and new capture?

@nhoizey
Copy link
Contributor

nhoizey commented May 24, 2023

Another option (probably useful but not enough) might be to support a syntax similar to attribute selectors.

For elements with view-transition-name: box-1, view-transition-name: box-2, etc.:

::view-transition-old(^=box-) {
  /* … */
}

@matthewp
Copy link

matthewp commented Jun 28, 2023

Similar to @nhoizey's idea, could be supporting * as a wildcard identifier like so:

::view-transition-old(box-*) {
  /* … */
}

@jakearchibald
Copy link
Contributor Author

jakearchibald commented Jun 28, 2023

Any use of characters that are currently valid within identifiers would be backwards incompatible. I haven't checked if that includes *.

@matthewp
Copy link

Is this feature intended to solve the sharing problem with view transition animations? Currently if I want to share some animation, for example a slide animation, the best way I can think to do so is to create a custom property like: --transition-slide on the root. However to use this animation requires adding at few different selectors that then set the animation.

I think with this proposal I could instead share some CSS and to use the animation only requires setting the view-transition-class. For example:

::view-transition-group(*, slide) {
  &::view-transition-old(*) {
    animation: ...
  }
  &::view-transition-new(*) {
    animation: ...
  }
}

Then usage:

.my-selector {
  view-transition-name: header;
  view-transition-class: slide;
}

Does this sound correct?

@bramus
Copy link
Contributor

bramus commented Aug 16, 2023

Similar to @nhoizey's idea, could be supporting * as a wildcard identifier like so:

::view-transition-old(box-*) {
  /* … */
}

I like this proposal. It would remove the need for an extra view-transition-class property. To address Jake’s remark: * is not allowed in an <ident-token> (spec) so it could just work.


An alternative idea to Jake’s original syntax suggestion that popped up: reuse the namespace selector syntax, like so:

.box {
  view-transition-class: box;
}

::view-transition-group(box|*) {
  /* … */
}

@matthewp
Copy link

I think I like the view-transition-class property the best because it makes it slightly easier to share animations. To share a slide animation you would just do:

.transition-slide {
  view-transition-class: slide;
}

The solutions that depend on view-transition-name naming conventions might conflict with other use-cases and tools that want you to name transitions a certain way.

@domchristie
Copy link

Similar to @nhoizey's idea, could be supporting * as a wildcard identifier like so:

Could this support selectors like: ::view-transition-old(box-*-title)?

I'm not familiar with the namespace selector syntax, so I'll have to look into that, but my initial thought was that this feature could reuse the attribute selector syntax.

@vmpstr
Copy link
Member

vmpstr commented Sep 14, 2023

One thing that an explicit class name gives us is an ability to say if you say view-transition-class: foo on a bunch of elements, but don't give them the name, then we can spec this to still match the element with itself, which avoids the need to come up with a whole bunch of unique names, you can just give a bunch of elements the same class.

@jakearchibald
Copy link
Contributor Author

jakearchibald commented Sep 14, 2023

But if you have two elements in the old state, all with the same class:

  • One
  • Two

And in the new state you have three elements, all with the same class:

  • One
  • One and a half
  • Two

How are they matched into groups?

Element equality won't work for MPA ever, and it'll become a gotcha with frameworks when their diffing algorithms don't quite do the right thing.

DOM order will fail for the above case.

@vmpstr
Copy link
Member

vmpstr commented Sep 15, 2023

This would only do element equality.

My comment was to address a different issue we've seen where people want to specifically match an element with itself in SPA without needing to come up with unique names for everything; something like view-transition-name: self.

view-transition-group can cause this behavior. If you need to match in some different ways, or in MPA, you are still free to give the element a unique view-transition-name

@jakearchibald
Copy link
Contributor Author

It might be useful. But, in order for it to work reliably in frameworks, you need to assign a key to elements so the diffing algorithm has a fighting chance of getting it right. And, if you're having to assign a key, you may as well assign a view-transition-name.

@vmpstr
Copy link
Member

vmpstr commented Sep 15, 2023

It might be useful. But, in order for it to work reliably in frameworks, you need to assign a key to elements so the diffing algorithm has a fighting chance of getting it right. And, if you're having to assign a key, you may as well assign a view-transition-name.

Agreed

The question is should we allow view-transition-group elements to match themselves, or should we create a new keyword like view-transition-name: self for this purpose

@bramus
Copy link
Contributor

bramus commented Oct 5, 2023

in order for it to work reliably in frameworks, you need to assign a key to elements so the diffing algorithm has a fighting chance of getting it right. And, if you're having to assign a key, you may as well assign a view-transition-name.

Rewinding a little bit back: I’m OK with having view-transition-group by itself work on only SPA.

When it is set:

  • The view-transition-name would be defined automagically
  • The value of the view-transition-group would be targetable within the pseudos. This would mean that the value must be unique among view-transition-group and view-transition-name.
::view-transition-new(card) {
  …
}

.card {
  view-transition-group: card;
}

To make this work with MPA, I think the solution lies in #9141 (dynamically construct <ident>s) combined with attr().

::view-transition-new(card) {
  …
}

.card {
  view-transition-group: card;
  view-transition-name: ident("card-", attr(id));
}

This way, across pages, the same view-transition-name can be given to the correct elements, without needing to duplicate a bunch of CSS. In SPA it can also be used, but is not mandatory, due to the automatic view-transition-name generation I mentioned earlier.

@vmpstr
Copy link
Member

vmpstr commented Oct 5, 2023

  • The value of the view-transition-group would be targetable within the pseudos. This would mean that the value must be unique among view-transition-group and view-transition-name.

This is problematic from the current spec perspective. Specifically, things like step 3.9.5 of pseudo element set up algorithm uses the name to generate a dynamic style sheet targeting ::view-transition-group(name) and set up specific transitions for that element. If that pseudo class now represents a group of elements, then we can't distinguish them for transform (etc) set up.

My thought is that presence of view-transition-group on its own should match to itself by generating some unique name (spelled out in the spec) something like ua-group-${group}-${index} where group is the group name and index is a unique number. Then the selection would have to have a different pseudo class or format: view-transition-group(*):group(groupname)? As an aside, maybe using class is easier since group already has a meaning in VT

@khushalsagar
Copy link
Member

This is problematic from the current spec perspective. Specifically, things like step 3.9.5 of pseudo element set up algorithm uses the name to generate a dynamic style sheet targeting ::view-transition-group(name) and set up specific transitions for that element. If that pseudo class now represents a group of elements, then we can't distinguish them for transform (etc) set up.

+1. We need some way for UA CSS to target the element. We can define something magical internally but doesn't seem like that's necessary.

My thought is that presence of view-transition-group on its own should match to itself by generating some unique name (spelled out in the spec) something like ua-group-${group}-${index} where group is the group name and index is a unique number. Then the selection would have to have a different pseudo class or format: view-transition-group(*):group(groupname)?

I like this direction. Ideal would be if this new CSS property also helps you target a set of generated pseudo-elements. So something like:

.foo {
   view-transition-class: target;
}

.bar {
   view-transition-class: target;
   view-transition-name: my-element;
}

In the example above, the UA computes a view-transition-name using the class for foo. Authors can query the name using getComputedStyle() if they need to add custom animation for any particular node. For bar, the specified view-transition-name is used. But in both cases, the class can be used to apply styles.

::view-transition-group(*):class(target) {
   ...
}

@noamr
Copy link
Collaborator

noamr commented Oct 10, 2023

Instead of adding a new property, suggesting to allow multiple idents in view-transition-name (space or comma separated), with the following semantics:

  • For the purposes of matching the old and new element, the combination of idents need to be unique and an exact match
  • For the purposes of selecting pseudo-elements, any of the idents has to match

So for example, a song in a playlist can have the following rule:
view-transition-name: song1 song-item

It would only match an element in the new state if it's also song1 song-item (or song-item song1),
but it would match either ::view-transition-group(song-item) or ::view-transition-group(song1) (or ::view-transition-old for that matter).

Together with a solution for #8320, this can solve the use-case without having to invent new syntax for the pseudo-element selector.

@khushalsagar
Copy link
Member

this can solve the use-case without having to invent new syntax for the pseudo-element selector.

The plan above SGTM. Re: inventing new syntax, I'm actually ok if we allow a comma separated list so a selector would apply if any of the items in the list are part of that pseudos view-transition-name set. That'll make it match active-view-transition's syntax.

@khushalsagar
Copy link
Member

Documenting a couple of syntax ideas below for this:

  • A new view-transition-class CSS property paired with a new pseudo-class for targeting the pseudo-elements.

       .item {
           view-transition-class: item, song;
           view-transition-name: song;
       }
       
       /* view-transition-class applies if any of the idents match the corresponding view-transition-class */
       html::view-transition-group(*):view-transition-class(item, artist) {
             ...
       }
       ```
  • Expand on view-transition-name and existing pseudo-element syntax.

       .item {
           /* The set of names must be unique and used for element matching. */
           view-transition-name: song, item;
       }
       
       /* Matches if any of the idents match the corresponding view-transition-name */
       html::view-transition-group(song, artist) {
             ...
       }
       ```

@noamr
Copy link
Collaborator

noamr commented Oct 11, 2023

OK I like

Documenting a couple of syntax ideas below for this:

  • A new view-transition-class CSS property paired with a new pseudo-class for targeting the pseudo-elements.
       .item {
           view-transition-class: item, song;
           view-transition-name: song;
       }
       
       /* view-transition-class applies if any of the idents match the corresponding view-transition-class */
       html::view-transition-group(*):view-transition-class(item, artist) {
             ...
       }
  • Expand on view-transition-name and existing pseudo-element syntax.
       .item {
           /* The set of names must be unique and used for element matching. */
           view-transition-name: song, item;
       }
       
       /* Matches if any of the idents match the corresponding view-transition-name */
       html::view-transition-group(song, artist) {
             ...
       }

OK this can work. I'm OK with bringing something like these two options to the csswg,.

@vmpstr
Copy link
Member

vmpstr commented Oct 11, 2023

Sounds good to me as well.

With the types proposal, would this all look like

html:active-view-transition(foo)::view-transition-group(bar):view-transition-class(baz) { ... }

?

We should bikeshed at some point 😃

@bramus
Copy link
Contributor

bramus commented Dec 12, 2023

  • Potential syntax for the selector:
1. ::view-transition-group(*.class1.class2)
2. ::view-transition-group(*).class1.class2
3. ::view-transition-group(* of class1 class2)
4. ::view-transition-group(* / class1 class2)

Some arguments:

  • (2) might be extendible to more generic pseudo-element classes in the future. However it feels like it would work with regular selectors (e.g. .class1).

I like the options where the class goes in the pseudo better. The view-transition-name also goes into the pseudo after all

  • (3) seems awkward when there is more than 1 class?

The “regular” of syntax in nth-* uses commas to separate the values. E.g. ::view-transition-group(* of class1, class2)

  • (4) it's not obvious whether it's name/class or class/name

What I like about this syntax is that it would be (almost*) identical to what you type as a value for the proposed shorthand: first the v-t-name and then the v-t-class(es). This should be the same here, so you’re good once you know it.

(*) only difference is that you need to replace auto from the shorthand by * in the pseudos when you only care about the v-t-classes.

@nmn
Copy link

nmn commented Dec 13, 2023

I agree on @bramus on all points here.

@noamr
Copy link
Collaborator

noamr commented Dec 13, 2023

  • Potential syntax for the selector:
1. ::view-transition-group(*.class1.class2)
2. ::view-transition-group(*).class1.class2
3. ::view-transition-group(* of class1 class2)
4. ::view-transition-group(* / class1 class2)

Some arguments:

  • (2) might be extendible to more generic pseudo-element classes in the future. However it feels like it would work with regular selectors (e.g. .class1).

I like the options where the class goes in the pseudo better. The view-transition-name also goes into the pseudo after all

  • (3) seems awkward when there is more than 1 class?

The “regular” of syntax in nth-* uses commas to separate the values. E.g. ::view-transition-group(* of class1, class2)

That would have any semantics (in this case, either class1 or class2 would be selected).

  • (4) it's not obvious whether it's name/class or class/name

What I like about this syntax is that it would be (almost*) identical to what you type as a value for the proposed shorthand: first the v-t-name and then the v-t-class(es). This should be the same here, so you’re good once you know it.

(*) only difference is that you need to replace auto from the shorthand by * in the pseudos when you only care about the v-t-classes.

Yea but we could also change the shorthand proposal to match what we decide here, e.g. name.class.class instead of name / class class

@bramus
Copy link
Contributor

bramus commented Dec 13, 2023

The “regular” of syntax in nth-* uses commas to separate the values. E.g. ::view-transition-group(* of class1, class2)

That would have any semantics (in this case, either class1 or class2 would be selected).

O yeah, I see … with the comma, people will interpret it as an or.

Yea but we could also change the shorthand proposal to match what we decide here, e.g. name.class.class instead of name / class class

I wouldn’t mix selector syntax (classes) with v-t-classname. After all, we also don’t require the v-t-name to be in the form of an id selector. E.g. it’s not ::view-transition-group(#hero) but ::view-transition-group(hero) since you can perfectly add hero as a v-t-name onto an element that is :not(#hero).

@jakearchibald
Copy link
Contributor Author

In terms of old/new classes, here's how it can happen:

const element = document.querySelector('.whatever');
element.style.viewTransitionName = 'foo';
element.style.viewTransitionClass = 'class-before';

document.startViewTransition(() => {
  element.style.viewTransitionClass = 'class-after';  
});

The above will result in ::view-transition-group(foo), but the question is which of the classes can be used to select that group?

#8319 (comment) proposes class-after only.

But in this case:

const element = document.querySelector('.whatever');
element.style.viewTransitionName = 'foo';
element.style.viewTransitionClass = 'class-before';

document.startViewTransition(() => {
  element.remove();
});

#8319 (comment) proposes class-before works.

This pattern could be summarised as "last captured classes win".

@noamr in this case:

const element = document.querySelector('.whatever');
element.style.viewTransitionName = 'foo';
element.style.viewTransitionClass = 'class-before';

document.startViewTransition(() => {
  element.style.viewTransitionClass = 'none';
});

…assuming none unsets the value, the group would have no classes. Is that right?

@noamr
Copy link
Collaborator

noamr commented Dec 13, 2023

This pattern could be summarised as "last captured classes win".

@noamr in this case:

const element = document.querySelector('.whatever');
element.style.viewTransitionName = 'foo';
element.style.viewTransitionClass = 'class-before';

document.startViewTransition(() => {
  element.style.viewTransitionClass = 'none';
});

…assuming none unsets the value, the group would have no classes. Is that right?

Exactly. The mental model is that the class is for the entire view-transition group (pair or single element). So setting it sets it for the group, unsetting it unsets it for the group.

@nmn
Copy link

nmn commented Dec 13, 2023

This pattern could be summarised as "last captured classes win".

This explanation was very helpful. Thank you!

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-view-transitions-2] Creating 'classes' of transition groups, and agreed to the following:

  • RESOLVED: Go with option 1 for the syntax in creating view-transition-class
The full IRC log of that discussion <frances_> Alan: next issue is 8319 on classes of transition groups
<frances_> vladimir: have view-transition-name, can be repetitive, introduce view-transition-class to apply to multiple elements, and prevent repeating. Separate list of custom items to name the classes. For the selection, the view-transition pseudo-element can select the class, few options listed in the issue on how to select it such as dots in brackets, dots outside the brackets. No strong preference.
<fantasai> https://github.com//issues/8319#issuecomment-1852207709
<frances_> vladimir: the semantics will be the last capture wins. If exit transition, the old capture will win, if an entry transition, class overrides old element.
<frances_> Alan: any objections?
<TabAtkins> q+
<frances_> Alan: any opinions?
<astearns> ack TabAtkins
<khush> q+
<frances_> tab: use case for class sounds great, good idea, for syntax lean towards class selector syntax into pseudo elements, least clumsy, names and classes, syntaxically distinguished
<frances_> Alan: which option is it?
<frances_> vladimir: option 1?
<frances_> Vladimir: option 1.
<astearns> ack khush
<TabAtkins> ::view-transition-group(*.class1.class2)
<frances_> khush: second what tab said, like option 2, if dark-class is a prefix, then will be a subtle difference in class declaration
<frances_> tab: true for hover pseudo class in dom class, for css selector syntax, doesn't allow it, prefer option 1
<TabAtkins> :hover::before and ::before:hover are currently both valid and mean different things in exactly that way
<vmpstr> s/dark-class/dot class/
<khush> ^ I +1'd option 1. :)
<astearns> ack florian
<astearns> ack fantasai
<frances_> fantasai: comment from someone in thread to introduce shorthand for view-transition-name and view-transition-class, can be reflected in both. Possibly between 1 and 4.
<frances_> Alan: any other opinions?
<fantasai> s/can be reflected in/can choose a syntax that works for/
<fantasai> s/Possibly between/That would likely be option 4. But I like both/
<frances_> Alan: Possibly resolve on option 1
<frances_> vladimir: yes
<frances_> PROPOSAL: Go with option 1 for the syntax in creating view-transition-class
<frances_> Alan: any objections?
<frances_> RESOLVED: Go with option 1 for the syntax in creating view-transition-class

@nmn
Copy link

nmn commented Jan 4, 2024

Can someone clarify what option 1 looks like? Great to see alignment on this.

@astearns
Copy link
Member

astearns commented Jan 4, 2024

@nmn from #8319 (comment) above

1. ::view-transition-group(*.class1.class2)

@jakearchibald
Copy link
Contributor Author

jakearchibald commented Jan 4, 2024

@fantasai

comment from someone in thread to introduce shorthand for view-transition-name and view-transition-class

I'm not against a shorthand here, but I'm not sure it would be useful. In this thread, code examples are like:

.box-1 {
  view-transition-name: box-1;
  view-transition-class: box;
}

.box-2 {
  view-transition-name: box-2;
  view-transition-class: box;
}

But I think real usage will be more like:

.box-1 {
  view-transition-name: box-1;
}

.box-2 {
  view-transition-name: box-2;
}

.box {
  view-transition-class: box;
}

Assuming markup like:

<div class="box box-1"></div>
<div class="box box-2"></div>

@nmn
Copy link

nmn commented Jan 4, 2024

QQ: view-transition-name will be optional right? It'll be possible to add just view-transition-class to elements?

@noamr
Copy link
Collaborator

noamr commented Jan 4, 2024

QQ: view-transition-name will be optional right? It'll be possible to add just view-transition-class to elements?

Still required, classes are one step towards no needing it but not the only step.

@jakearchibald
Copy link
Contributor Author

I'm not aware of another way of associating two different elements across documents than some kind of identifier, but I might just lack imagination.

@vmpstr
Copy link
Member

vmpstr commented Jan 4, 2024

Cross document, I also don't see another way to associate elements. For same document transitions though, there is a possibility of using element object itself as the matching condition. I know this doesn't play too well with frameworks, but I believe at least some frameworks have an ability for the developer to hint that some elements have to stay the same.

But I agree with @noamr, there are plenty of discussion to be had here. I'd prefer that we close out this issue with the spec edits for view-transition-class and the corresponding selector and open up a new issue for automatic matching

@khushalsagar
Copy link
Member

I believe at least some frameworks have an ability for the developer to hint that some elements have to stay the same.

#8320 is the relevant issue for this.

@nmn
Copy link

nmn commented Jan 4, 2024

OK. That makes sense. We can generate unique IDs for each element, so the requirement is OK.

noamr added a commit to noamr/csswg-drafts that referenced this issue Jan 8, 2024
A `view-transition-class` property allows specifying view-transition pseudo-elements
that apply to multiple participating elements without having to replicate them.

In this PR:
- Specifying `view-transition-class`
- Extending the pseudo-element syntax to support `::view-transition-foo(name.class)`
- Extending the capture algorithms and data structure to capture the classes
- Added a simple example for using classes

Based on [this resolution](w3c#8319 (comment)).
Closes w3c#8319
noamr added a commit to noamr/csswg-drafts that referenced this issue Jan 25, 2024
A `view-transition-class` property allows specifying view-transition pseudo-elements
that apply to multiple participating elements without having to replicate them.

In this PR:
- Specifying `view-transition-class`
- Extending the pseudo-element syntax to support `::view-transition-foo(name.class)`
- Extending the capture algorithms and data structure to capture the classes
- Added a simple example for using classes

Based on [this resolution](w3c#8319 (comment)).
Closes w3c#8319
noamr added a commit that referenced this issue Jan 31, 2024
…nding algorithms (#9773)

* [css-view-transitions-2] Add 'view-transition-class'

A `view-transition-class` property allows specifying view-transition pseudo-elements
that apply to multiple participating elements without having to replicate them.

In this PR:
- Specifying `view-transition-class`
- Extending the pseudo-element syntax to support `::view-transition-foo(name.class)`
- Extending the capture algorithms and data structure to capture the classes
- Added a simple example for using classes

Based on [this resolution](#8319 (comment)).
Closes #8319

---------

Co-authored-by: Khushal Sagar <[email protected]>
Co-authored-by: vmpstr <[email protected]>
@bramus bramus removed the Needs Edits label Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-view-transitions-2 View Transitions; New feature requests
Projects
None yet