w3c / csswg-drafts

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

[css-view-transitions-2] view-transition-name determined by element #8320

Open jakearchibald opened 1 year ago

jakearchibald commented 1 year ago

Thinking of this example https://codepen.io/jaffathecake/pen/VwBprqL

In this case the developer has to manually give each list item a unique view-transition-name. This could be avoided via something like:

.list-item {
  view-transition-name: element-uuid();
}

Where element-uuid() returns a value unique and constant for that DOM node.

The proposal for CSS random() has a per-element value that could be generalised as element-uuid(). https://github.com/w3c/csswg-drafts/issues/2826#issuecomment-1204305712 (cc @tabatkins)

The downside to this is it would only work for same-document transitions, since element-uuid()s will always be different across documents. It also depends on element usage being consistent, which some frameworks don't always get right.

Edit: This would depend on https://github.com/w3c/csswg-drafts/issues/8319, otherwise styling the resulting pseudos is impossible.

mirisuzanne commented 1 year ago

As @chriscoyier pointed out recently, this solution doesn't really give authors a way to define transitions for the unique names that are generated. How would an author write ::view-transition-* selectors to target these elements with dynamically generated names? In your example, you use the * name selector, but that would target all view transitions on the page. Ideally, we'd be able to target just list-items. Maybe I'm missing something that already makes that possible?

jakearchibald commented 1 year ago

I think that's a separate feature https://github.com/w3c/csswg-drafts/issues/8319, but yes, you would need that feature before this one. I should have mentioned it in the OP.

tabatkins commented 1 year ago

I don't think we actually want this to be a generic feature, for a few reasons.

  1. As Miriam/#8319 points out, having it be just the randomly-generated identifier isn't actually very useful, and you instead want to be able to group them in some targetable way while giving them unique identities. That's something fairly specific to view-transitions.
  2. As @dbaron points out over in the Toggle repo, having computed styles be unique per element defeats some style-sharing optimizations that are pretty nice to keep. In the toggle case, the common case we need to care about is referencing the sibling index of the element, which we can defer resolving until used-value time; I think this would fall into the same bucket, where all you need at computed-value time is the idea that the names are unique in some way, but they don't actually need to become unique until used-value time.

So I instead recommend having view-transitions define a custom way of saying "this thing has a name, but also is unique based on element identity in a way that's not exposed directly but does affect before/after matching". Maybe just view-transition-name: foo unique; or foo per-element?

jakearchibald commented 1 year ago

I'm happy to sit on this one for a bit. https://github.com/w3c/csswg-drafts/issues/8319 is higher priority.

noamr commented 11 months ago

Suggesting that a solution to this should take https://github.com/w3c/csswg-drafts/issues/8319#issuecomment-1756046626 into account, and build upon #9141 as a way to dynamically generate unique idents.

For example, given a playlist of songs, all of the songs can have a song view-transition-name, but also each one of them needs a unique one (if for example this is a sort animation). So the song would need something like a song-123 name.

The idea in #9141 is to concat it from attributes, e.g. view-transition-name: ident("song-" attr(id)). The big advantage over using something like element-uuid() is that this can work across documents - you generate the ID from existing HTML data rather than from some internal browser state that can't carry over between documents.

This is also flexible in a way that DOM order can be used instead of attributes, e.g. with the counter() function: view-transition-name: ident("item-" counter(list-foo)) or any result of a calc: ident("item-" mod(var(--something), 3).

khushalsagar commented 8 months ago

@tdresser shared an interesting observation today, frameworks like React have a concept of a unique key for list items : https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key. It seems like instead of a native API, where we can only assume element identify via the corresponding Node, it would be cool to leverage such concepts within the frameworks. Maybe add an API which makes hooking up a custom attribute to the CSS property value easier?

mirisuzanne commented 8 months ago

As someone who is very often not using one of these frameworks, it doesn't feel like a great solution to me.

jakearchibald commented 8 months ago

@mirisuzanne are you otherwise happy with a feature that would only work same-document transitions?

noamr commented 8 months ago

I wonder what people think about an idea like this:

section.list li.item[:id] img { view-transition-name: item-[:id] }

Where the attributes are captured in the selector chain and then used to generate a name.

mirisuzanne commented 8 months ago

Oh, my happiness. Interesting… :)

I'm not actually opposed to a feature that makes it easier for third-party tools to assist in the cross-document functionality. I just hope we also keep working towards a solution that doesn't rely on frameworks?

@noamr I think your proposal still has the cross document issue? Those IDs would be specific to a given document?

noamr commented 8 months ago

Oh, my happiness. Interesting… :)

I'm not actually opposed to a feature that makes it easier for third-party tools to assist in the cross-document functionality. I just hope we also keep working towards a solution that doesn't rely on frameworks?

@noamr I think your proposal still has the cross document issue? Those IDs would be specific to a given document?

You can have elements with the same IDs on the new document, eg if the IDs come from some database.

bramus commented 8 months ago

(#) I wonder what people think about an idea like this:

section.list li.item[:id] img { view-transition-name: item-[:id] }

Where the attributes are captured in the selector chain and then used to generate a name.

Interesting. I like how this allows you to capture an attribute from a parent element and use that on a child. With attr() that’s not immediately possible, unless you jump through some hoops using a custom property.

Two thoughts:

noamr commented 8 months ago

(#) I wonder what people think about an idea like this: section.list li.item[:id] img { view-transition-name: item-[:id] } Where the attributes are captured in the selector chain and then used to generate a name.

Interesting. I like how this allows you to capture an attribute from a parent element and use that on a child. With attr() that’s not immediately possible, unless you jump through some hoops using a custom property.

Two thoughts:

Isn't the ident function redundant though? why not "item-" @id or item-(@id) or something like that?

jakearchibald commented 8 months ago

One issue with that syntax is it allows you to capture an attribute named n from only one element in the chain. As in, you can't do: .one[@foo] .two[@foo], because now you have two @foo and no way to differentiate them.

I don't like the naming/syntax here, but you could do something like:

:capture-for-attr(.one, 'one') :capture-for-attr(.two, 'two') {
  view-transition-name: captured-attr('one', 'foo') captured-attr('two', 'foo');
}

:capture-for-attr(selector, name) - where selector is processes as normal, but the selected element is stored as name.

captured-attr(name, attribute) - like attr(), but the first arg is the name of something captured earlier in the selector.

This all feels very complicated though.

noamr commented 8 months ago

I think that if we wanted to support multiple attributes with the same name in the future we could add something like:

.one[@id1:id] .two[@id2:id] { view-transition-name: "something" @id1 @id2 } 

(JS destructuring assignments have the same problem and a similar sollution)

If we go with verbose function-style I'd rather use the more conservative attr+CSS-variables.

bramus commented 7 months ago

A while back I ran a poll (Twitter, Mastodon) asking authors what is missing from View Transitions. Out of the 33 replies, 6 requested this feature, making it the number 1 request (along with retargetable transitions and scoped transitions)

mirisuzanne commented 7 months ago

I'm curious what the .foo[@id] selector syntax adds in this proposal. On the other end, item-[@id] seems like a nice concise syntax equivalent to e.g. item- attr(id, ident)). If we can access attributes as idents (or interpolate them into idents), I'm not sure if the selector-side syntax bring anything new to the equation? Having a value-side shorthand for accessing attributes as idents might still be useful - it's certainly simpler to write.

noamr commented 7 months ago

I'm curious what the .foo[@id] selector syntax adds in this proposal. On the other end, item-[@id] seems like a nice concise syntax equivalent to e.g. item- attr(id, ident)). If we can access attributes as idents (or interpolate them into idents), I'm not sure if the selector-side syntax bring anything new to the equation? Having a value-side shorthand for accessing attributes as idents might still be useful - it's certainly simpler to write.

Which ID would you pick, given:

section[id] > li[id] label * { view-transition-name: item-[@id] }`

Without @ at the selector we'd have to assume that you mean the attribute on the selected element, but that's not always the case.

jensimmons commented 5 months ago

I'm making a simple demo that uses View Transitions to animate enlarging an item that's laid out with CSS Grid, and shifting the other items in the grid around. I was surprised to learn that 1) I have to use JavaScript to use View Transitions, and 2) that I have to give each item in the Grid a unique view-transition-name, and there's no mechanism for applying the functionality to all items.

This caused me to have to write this code:

    .card:nth-child(1) { view-transition-name: card-1; }
    .card:nth-child(2) { view-transition-name: card-2; }
    .card:nth-child(3) { view-transition-name: card-3; }
    .card:nth-child(4) { view-transition-name: card-4; }
    .card:nth-child(5) { view-transition-name: card-5; }
    .card:nth-child(6) { view-transition-name: card-6; }
    .card:nth-child(7) { view-transition-name: card-7; }
    .card:nth-child(8) { view-transition-name: card-8; }
    .card:nth-child(9) { view-transition-name: card-9; }
    .card:nth-child(10) { view-transition-name: card-10; }
    .card:nth-child(11) { view-transition-name: card-11; }
    .card:nth-child(12) { view-transition-name: card-12; }
    .card:nth-child(13) { view-transition-name: card-13; }
    .card:nth-child(14) { view-transition-name: card-14; }
    .card:nth-child(15) { view-transition-name: card-15; }
    .card:nth-child(16) { view-transition-name: card-16; }
    .card:nth-child(17) { view-transition-name: card-17; }
    .card:nth-child(18) { view-transition-name: card-18; }
    .card:nth-child(19) { view-transition-name: card-19; }
    .card:nth-child(20) { view-transition-name: card-20; }
    .card:nth-child(21) { view-transition-name: card-21; }
    .card:nth-child(22) { view-transition-name: card-22; }
    .card:nth-child(23) { view-transition-name: card-23; }
    .card:nth-child(24) { view-transition-name: card-24; }
    .card:nth-child(25) { view-transition-name: card-25; }
    .card:nth-child(26) { view-transition-name: card-26; }
    .card:nth-child(27) { view-transition-name: card-27; }
    .card:nth-child(28) { view-transition-name: card-28; }
    .card:nth-child(29) { view-transition-name: card-29; }
    .card:nth-child(30) { view-transition-name: card-30; }
    .card:nth-child(31) { view-transition-name: card-31; }
    .card:nth-child(32) { view-transition-name: card-32; }
    .card:nth-child(33) { view-transition-name: card-33; }
    .card:nth-child(34) { view-transition-name: card-34; }
    .card:nth-child(35) { view-transition-name: card-35; }
    .card:nth-child(36) { view-transition-name: card-36; }
    .card:nth-child(37) { view-transition-name: card-37; }
    .card:nth-child(38) { view-transition-name: card-38; }
    .card:nth-child(39) { view-transition-name: card-39; }
    .card:nth-child(40) { view-transition-name: card-40; }
    .card:nth-child(41) { view-transition-name: card-41; }
    .card:nth-child(42) { view-transition-name: card-42; }
    .card:nth-child(43) { view-transition-name: card-43; }
    .card:nth-child(44) { view-transition-name: card-44; }
    .card:nth-child(45) { view-transition-name: card-45; }
    .card:nth-child(46) { view-transition-name: card-46; }
    .card:nth-child(47) { view-transition-name: card-47; }
    .card:nth-child(48) { view-transition-name: card-48; }
    .card:nth-child(49) { view-transition-name: card-49; }
    .card:nth-child(50) { view-transition-name: card-50; }

Which is not robust — what if there are more than 50 items on the Grid?? The experience will break.

It seems View Transitions was designed with the expectation that websites are JavaScript first — that it's fine if every items needs to be uniquely named, a developer can just use JS to create all the HTML, and inline styles with JS-created names.

jensimmons commented 5 months ago

This would be a much better solution: .card { view-transition-name: auto; }

noamr commented 5 months ago

This would be a much better solution: .card { view-transition-name: auto; }

See discussion above - the problem with this solution is that it doesn't work for cross-document, or if the framework recreates the element. Generating the name from attributes is perhaps more verbose but works for all those use-cases.

bramus commented 5 months ago

It seems View Transitions was designed with the expectation that websites are JavaScript first

This is only the case for Same-Document View Transitions. For Cross-Document View Transitions JavaScript is not mandatory.

(And, in the future, there could always be worked on defining some non-JS based triggers for Same-Document View Transitions)

This would be a much better solution: .card { view-transition-name: auto; }

While that could solve some Same-Document View Transitions, this solution won’t work for:

This is also discussed earlier in this thread.

Also see the discussions in https://github.com/w3c/csswg-drafts/issues/9639 and https://github.com/w3c/csswg-drafts/issues/9141 which are concerned with solving this naming problem.

noamr commented 5 months ago

Another syntax option for generating the name from an ID:

section[--id: id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

So instead of using attr or a special syntax for concating the ident, we only have a special syntax in the selector that assigns the attribute value to a CSS custom property, and view-transition-name can accept a concatentation of literal strings and functions that generate strings/numbers.

romainmenke commented 5 months ago

I don't really like the idea of having extra syntax here specifically for view transitions. I also don't like that selectors then suddenly have side effects.

section[--id: id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

could be :

section[id]:has(img.thumbnail) {
    --id: attr(id);
}

section[id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

sidetrack I also can't help but feel that user should be able to use the full selector syntax to select view transition pseudo elements and that we are coming at this from the wrong angle. As I see it we actually want to have classes, attributes and id's on pseudo elements which is not possible because authors can add these in markdown. But what if we allow authors to push this information onto them? ```css /* ignore the exact naming and syntax */ .foo { view-transition-id: "something-unqiue"; view-transition-class: "class-a" "class-b" "class-d"; view-transition-attributes: ["foo" "bar"] ["fooz" "baz"] } ```
jakearchibald commented 5 months ago

@jensimmons

Which is not robust — what if there are more than 50 items on the Grid?? The experience will break.

It will also break if you remove the first item in the grid, because it'll assume the first item in the grid before and after the change are the same grid item, but they are not. This is why you need an identity-based system.

In SPAs, you could say that the element instance is the identity. However, that isn't how it ends up in many SPA implementations, and it's never how it ends up in MPA, since no elements are the same instance across documents.

I was surprised to learn that 1) I have to use JavaScript to use View Transitions

Out of curiosity, how were you modifying the grid in this example?

noamr commented 5 months ago

I don't really like the idea of having extra syntax here specifically for view transitions.

It can be used in the future for any other feature that uses idents.

I also don't like that selectors then suddenly have side effects.

That's fair

section[--id: id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

could be :

section[id]:has(img.thumbnail) {
    --id: attr(id);
}

section[id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

Yea, this was proposed as an alternative earlier in the thread. It's a bit more verbose, but I'm fine with it if we go down this more conservative path. What always felt off to me about attr is that attributes are an HTML concept, which already exists in selectors, and here we put it into the declaration, creating a bit of a redundancy. But since attr() already (sort of) exists perhaps that ship has sailed?

sidetrack I also can't help but feel that user should be able to use the full selector syntax to select view transition pseudo elements and that we are coming at this from the wrong angle.

As I see it we actually want to have classes, attributes and id's on pseudo elements which is not possible because authors can add these in markdown.

But what if we allow authors to push this information onto them?

/* ignore the exact naming and syntax */
.foo {
    view-transition-id: "something-unqiue";
    view-transition-class: "class-a" "class-b" "class-d";
    view-transition-attributes: ["foo" "bar"] ["fooz" "baz"]
}

We already have view-transition-name (unique) and view-transition-class (which is kind of like how you put it here). We can discuss having attributes as well but perhaps it's indeed a new issue. Determining the name by element is not about pseudo-elements, it's about matching the old and new states.

jakearchibald commented 5 months ago

@romainmenke

I also can't help but feel that user should be able to use the full selector syntax to select view transition pseudo elements

Here's an example of a selector for a view transition pseudo element: ::view-transition-new(header) - can you describe how that isn't 'full'? I don't know if this bit of detail is useful but: Remember that view transition pseudos may be representing something no longer in the document.

But what if we allow authors to push this information onto them?

/* ignore the exact naming and syntax */
.foo {
    view-transition-id: "something-unqiue";
    view-transition-class: "class-a" "class-b" "class-d";
    view-transition-attributes: ["foo" "bar"] ["fooz" "baz"]
}

Can you describe what the effect of this code is? As in, how would you explain it to a developer? I'm happy to ignore the syntax and naming but I have no idea what it does.

romainmenke commented 5 months ago

Can you describe what the effect of this code is? As in, how would you explain it to a developer? I'm happy to ignore the syntax and naming but I have no idea what it does.

It would assign classes, id's and attributes onto the pseudo elements.

::view-transition-new(.class-a[foo=bar]) {}

::view-transition-new(.class-b[foo~="bar"]) {}

::view-transition-new(.class-a:is([foo], [fooz])) {}

I really do not want to drag this too much off topic :) But if the issue is that authors need more powerful tools to select view transitions then I do wonder why we aren't leaning into the full selector syntax.

This is mostly in response to string concatenation suggested in view-transition-name: "thumb-" var(--id);

This is actually a key/value pair. thumb and id : [thumb=id]

noamr commented 5 months ago

How would this work in terms of matching old/new element? Regardless of pseudo-element selection, you still have to have a way to match the old and new element 1:1. @romainmenke

jakearchibald commented 5 months ago

@romainmenke the class part has already been done https://github.com/w3ctag/design-reviews/issues/938. I'm not sure what attributes add.

romainmenke commented 5 months ago

Classes aren't key/value pairs. You can't change part of it or select part of without a string concatenation mechanic, which CSS currently doesn't have.

But I labeled it as a sidetrack because I am really unsure if my analysis is correct here. Maybe these aren't key/value pairs at all. Maybe string concatenation is the correct solution.

jakearchibald commented 5 months ago

Classes aren't key/value pairs.

I really don't think that matters. People make classes that are thing--state all the time. There are whole patterns built around it. I don't see what problem is solved by adding a 'real' key-value system.

bramus commented 5 months ago

Whichever method we land on here, I would like to call out that we would need the ident() function from #9141 when dynamically constructing an ident.

It might not be relevant for view-transition-name, but would be for other properties, specifically shorthands.

Cross-posting this comment from #9141 to clarify:

Parsing purposes. Think of shorthands, such as scroll-timeline.

With ident():

  • scroll-timeline: inline ident("tl-" var(--id))
  • scroll-timeline: ident("tl-" var(--id)) inline

Without ident():

  • scroll-timeline: inline "tl-" var(--id)
  • scroll-timeline: "tl-" var(--id) inline

If --id in that example were block, you’d end up with scroll-timeline: inline "tl-" block when not using the ident() function. I don’t think the parser would like that 😅 (nor would I, as an author trying to read the code).

noamr commented 5 months ago

Whichever method we land on here, I would like to call out that we would need the ident() function from #9141 when dynamically constructing an ident.

It might not be relevant for view-transition-name, but would be for other properties, specifically shorthands.

Cross-posting this comment from #9141 to clarify:

Parsing purposes. Think of shorthands, such as scroll-timeline. With ident():

  • scroll-timeline: inline ident("tl-" var(--id))
  • scroll-timeline: ident("tl-" var(--id)) inline

Without ident():

  • scroll-timeline: inline "tl-" var(--id)
  • scroll-timeline: "tl-" var(--id) inline

If --id in that example were block, you’d end up with scroll-timeline: inline "tl-" block when not using the ident() function. I don’t think the parser would like that 😅 (nor would I, as an author trying to read the code).

I wonder if we can solve this for particular shorthands rather than add avoidable verbosity to view-transition-name.

nt1m commented 5 months ago

I wonder if attr() is usable here? I think if attr() is supported, it would probably address most use cases.

noamr commented 5 months ago

I wonder if attr() is usable here? I think if attr() is supported, it would probably address most use cases.

Yea, something like https://github.com/w3c/csswg-drafts/issues/8320#issuecomment-2044478188

section[id]:has(img.thumbnail) {
    --id: attr(id);
}

section[id] img.thumbnail {
    view-transition-name: "thumb-" var(--id);
}

If we converge on something like that it would be totally fine.

noamr commented 5 months ago

btw attr() used to have weird security issues (like having to disallow nonce), and we'll have to think about this with future security-sensitve attributes, that's why it was never shipped for other feature. If this still comes up, perhaps instead of a general-purpose attr we could enable data(foo) and id() ? Seems like id and custom data- attributes would be the mainly used attributes for this anyway.

khushalsagar commented 5 months ago

Supporting attr() with an allow list for which attributes can be referenced sounds neat!

noamr commented 5 months ago

To clarify, I meant having bespoke id() and data(foo) functions, which would be instead of attr(id) and attr(data-foo).

fantasai commented 5 months ago

I wonder if attr() is usable here? I think if attr() is supported, it would probably address most use cases.

+1 to using attr().

Also +1 to Jen's usecase and proposal ( https://github.com/w3c/csswg-drafts/issues/8320#issuecomment-2023077559 ) for auto.

See discussion above - the problem with this solution is that it doesn't work for cross-document, or if the framework recreates the element. Generating the name from attributes is perhaps more verbose but works for all those use-cases.

An auto keyword wouldn't address those use cases, but it would address the ones that Jen is trying to solve, which don't have this problem. If you're rebuilding the DOM, then yes, you will need to do something else. But if you're not -- and there are many interesting cases where where you're not -- we can make this a lot simpler for the author.

We could go further to address cross-document cases by relaxing the restriction that a name must be unique and come up with a matching algorithm that can handle multiple elements.

To address things that move or disappear, authors can use unique tags for those items. So let's say you have a list of 10 items and you're moving item 8 to be the 2nd item. You identify that item with a unique name, and the rest of the items with a generic one, and the UA matches them up in DOM order.

We might want to be able to scope the count within a subtree somehow, but I think this would work without requiring counting code in the HTML generator or in JS, and that seems like a usability win imho, particularly for lightweight websites.

bramus commented 5 months ago

(#) An auto keyword wouldn't address those use cases, but it would address the ones that Jen is trying to solve, which don't have this problem. If you're rebuilding the DOM, then yes, you will need to do something else. But if you're not -- and there are many interesting cases where where you're not -- we can make this a lot simpler for the author.

Do note that auto would work in Jen’s case as the code is not using any custom animations. Once an author wants to use a custom animation, auto itself can’t help you there. When everything is set to auto, how would you apply custom animations onto them: ::view-transition-group(?whatgoeshere?)?

It’s only when combined view-transition-class that view-transition-name: auto becomes really useful – something worth considering in WebKit’s case, as by the looks of it the current implementation seems to be scoped to L1 which does not include L2’s view-transition-class.

(#) To address things that move or disappear, authors can use unique tags for those items. So let's say you have a list of 10 items and you're moving item 8 to be the 2nd item. You identify that item with a unique name, and the rest of the items with a generic one, and the UA matches them up in DOM order.

Removing nodes is not covered by this. Take a list of 10 items where you remove item 2 wrapped in a View Transition. All items after the original item 2 get the numbers 3-10 in the old snapshot but 2-9 in the new snapshot – they won’t line up.

(IIRC random() would be able to correctly cover this, as you can pin a generated value on a node)


This to say that the story for view-transition-name: auto would be: “Yeah you can use that, but not if you use framework X, Y, or Z; not if you are removing nodes; not if you want to apply custom animations in browser X because they have implemented L1; and not for MPA.” – seems a bit weird to add a feature that only works in a limited number of cases.

The ident() + attr() approach does not have these limitations.

noamr commented 5 months ago

I wonder if attr() is usable here? I think if attr() is supported, it would probably address most use cases.

+1 to using attr().

Great!

Also +1 to Jen's usecase and proposal ( #8320 (comment) ) for auto.

See discussion above - the problem with this solution is that it doesn't work for cross-document, or if the framework recreates the element. Generating the name from attributes is perhaps more verbose but works for all those use-cases.

An auto keyword wouldn't address those use cases, but it would address the ones that Jen is trying to solve, which don't have this problem. If you're rebuilding the DOM, then yes, you will need to do something else. But if you're not -- and there are many interesting cases where where you're not -- we can make this a lot simpler for the author.

It would only address the use case of animating grid positions if the underlying mechanism used for it is an SPA, with a framework that doesn't replace elements. If, for example, this was an MPA where every "current image" had its own page, the author would have to refactor this use case if they used auto.

In other words, where auto is enough is not a matter of use case, but a matter of underlying mechanism, or "how your app is built". The problem is that underlying mechanisms can be replaced, and having to refactor view-transition-name: auto values that are sprinkled around a CSS code base might make people feel that perhaps they should have used attr() + ident() in the first place. This will be even more apparent in use cases that feel less obvious, like an expanding header. auto would work when the app mechanism is simple, and then when the mechanism changes the author would incur an additional cost because they went with the simpler approach that's less portable by design.

We could go further to address cross-document cases by relaxing the restriction that a name must be unique and come up with a matching algorithm that can handle multiple elements.

Can you give an example for such algorithm? Or you mean that what's below here is that example?

To address things that move or disappear, authors can use unique tags for those items. So let's say you have a list of 10 items and you're moving item 8 to be the 2nd item. You identify that item with a unique name, and the rest of the items with a generic one, and the UA matches them up in DOM order.

This feels simple but actually adding complexity, because it overloads auto with opinionated behavior that authors have to remember. ident("item-" counter()) or so is much simpler to understand because it's neutral. CSS frameworks can take the role of adding opinionated stuff on top of this for ergonomics, and if we see patterns emerge there we can consider adding something like that into the platform.

bramus commented 4 months ago

To show the net effect the proposed addition of ident()+attr() will have on authors I have created comparison demos:

  1. Current way of naming multiple things: https://codepen.io/bramus/pen/xxmozvN
  2. Proposed way of automatically naming multiple things with ident() and attr(): https://codepen.io/bramus/pen/PogVZwb

(The second demo includes a rough polyfill to make it actually work. For best effect use Chrome Canary as these demos also use view-transition-class)

I was able to remove 100 LOC in the second demo and can now change markup (i.e. add / remove items) without needing to worry about the CSS.

Using view-transition-name: auto would not have worked here as I am doing the title transition between two different elements (from the h2 to a span or back, depending on the checked state of the input).

bramus commented 4 months ago

Agenda+’ing this one as I think it’s ready for discussion at the meeting.

Proposal:

Why not only attr()?

Why ident() and not allow setting some “strings” as the value?

Why not auto?

jakearchibald commented 4 months ago

Minor thought on auto vs element-uuid().

Imagine this, which uses the ident() proposal above:

.card {
  --card-id: element-uuid();
  view-transition-name: ident(var(--card-id));

  img {
    view-transition-name: ident(var(--card-id) "-img");
  }
}

This works as long as the card elements remain the same elements, but it works even if the img within is a different element.

That doesn't seem possible with auto.

fantasai commented 4 months ago

Do note that auto would work in Jen’s case as the code is not using any custom animations. Once an author wants to use a custom animation, auto itself can’t help you there. When everything is set to auto, how would you apply custom animations onto them: ::view-transition-group(?whatgoeshere?)?

Using view-transition-class, obviously.

(https://github.com/w3c/csswg-drafts/issues/8320#issuecomment-2060294154) To address things that move or disappear, authors can use unique tags for those items. So let's say you have a list of 10 items and you're moving item 8 to be the 2nd item. You identify that item with a unique name, and the rest of the items with a generic one, and the UA matches them up in DOM order.

Removing nodes is not covered by this. Take a list of 10 items where you remove item 2 wrapped in a View Transition. All items after the original item 2 get the numbers 3-10 in the old snapshot but 2-9 in the new snapshot – they won’t line up.

Not sure what's the problem... You label the item you're removing as "--special-item" and the rest as "--normal-item" and everything should line up just fine?

It would only address the use case of animating grid positions if the underlying mechanism used for it is an SPA, with a framework that doesn't replace elements.

This is fine. Not everyone is building an "SPA". View Transitions can be used to do nice animations for simpler changes within a web page, such as in @jensimmons's demo. Fancy counting functions and string concatenation for building out custom IDs is nice but... if you don't need them, why do we need to require them? We shouldn't require authors to use complicated mechanisms to do simple things.

In general, the CSSWG shouldn't be designing features for large complicated websites only, but also make things easy to use for smaller, simpler websites (and parts of websites) also. Authors can step up to more complicated solutions as they need them, we shouldn't force them into it prematurely.

khushalsagar commented 4 months ago

I +1 the idea of autogenerating names without requiring the author to assign an id to every element. But I prefer an approach which uses node identity to generate names over DOM ordering. The DOM ordering approach is more subtle in that small unrelated changes to the DOM can affect how elements are being matched up.

A node identity based approach is possible with both syntax options: view-transition-name: auto or view-transition-name: element-uuid(). I lean towards the latter because of the use-case @jakearchibald pointed to.

@fantasai node identity based generated names should work for the use-case @jensimmons brought up. Would you still want us to use DOM ordering for this..?

nt1m commented 4 months ago

Minor thought on auto vs element-uuid().

Imagine this, which uses the ident() proposal above:

.card {
  --card-id: element-uuid();
  view-transition-name: ident(var(--card-id));

  img {
    view-transition-name: ident(var(--card-id) "-img");
  }
}

This works as long as the card elements remain the same elements, but it works even if the img within is a different element.

That doesn't seem possible with auto.

Practically, I don't see how this is different from:

.card {
  view-transition-name: element-uuid();

  img {
    view-transition-name: element-uuid();
  }
}

In which case, auto would also do the job.

khushalsagar commented 4 months ago

^ view-transition-name: ident(var(--card-id) "-img"); version means even if the img node changes but the ancestor container card node remains the same, you still get a consistent name.

nt1m commented 4 months ago

I personally don't see much point in removing and recreating the img in this case instead of just mutating it (which is the only thing that would cause the uuid to change), but sure, this is something auto wouldn't easily do.