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-images-4] Allow transitions between different types within stripes() #8622

Open
SebastianZ opened this issue Mar 20, 2023 · 14 comments · May be fixed by #9767
Open

[css-images-4] Allow transitions between different types within stripes() #8622

SebastianZ opened this issue Mar 20, 2023 · 14 comments · May be fixed by #9767

Comments

@SebastianZ
Copy link
Contributor

SebastianZ commented Mar 20, 2023

In #8140 @fantasai changed the computed value of stripes() to say

The computed value of this function is an ordered list of stripes, each given as a computed color and a thickness represented either a <flex> value or a computed <length-percentage> value.

The reason behind that was to make it work for inherited properties.

Though that seems to come at the cost of not being able to transition between values.

At the same time, CSS Grid defines this:

<flex> values are not <length>s (nor are they compatible with <length>s, like some <percentage> values), so they cannot be represented in or combined with other unit types in calc() expressions.

Both result in disallowing transitions between values of different types.

In #8163 I provided an example of how those types could be computed. That would allow transitioning between them and animating them.

Taken from that issue:

How do you interpolate from stripes(#000 10px, #111 20px) to stripes(#222 10fr, #333 20fr)?

Possible answer: With a total width of 60px, the transition would be from stripes(#000 10px, #111 20px, transparent 30px) to stripes(#222 20px, #333 40px, transparent 0px). For 120px total width it would be from stripes(#000 10px, #111 20px, transparent 90px) to stripes(#222 40px, #333 80px, transparent 0px).

Also stripes(#111 calc(5px + 5fr), #222 calc(10px + 10fr)) could be made valid. With a total length of 60px again, it would be equivalent to stripes(#111 calc(5px + 15px), #222 calc(10px + 30px)). And assuming a total width of 120px, it would be the same as stripes(#111 calc(5px + 35px), #222 calc(10px + 70px)).

Sebastian

@tabatkins
Copy link
Member

Flex isn't interpolable with length in Grid because they cause trigger different layout behaviors, and figuring out a way to mix their effects with continuity would have been way too complicated for the benefit we'd get from it.

In stripes() flexes are much simpler (they just turn into lengths with a relatively trivial transform), so in theory they could be interpolable. You can't do it naively with a calc(), tho. Flex values are context-dependent, not just on the container size but also on the sum of all flex values in use in the context. A 10fr is large if it's accompanied by a 1fr, but small if accompanied by a 100fr. This means you have two general problems:

  1. If the sums of flexes are different between the starting and ending values, the intermediate values will not reproduce what you'd naively expect if you just resolved the flexes to lengths and interpolated them.

  2. 0fr has a fundamentally different behavior from any >0fr value.

We could fix both of these! For (1) we could normalize the flex sum to a fixed value (1fr?) at computed-value time. (This is probably a good idea anyway, since the currently-allowed interpolation of flex segments still suffers from this problem - 1fr, 4fr to 400fr, 100fr does not produce equal-sized stripes in the middle.) For (2) we could reproduce the behavior from flexbox where if the flex sum is <1, it represents only the corresponding portion of the free space, so .1fr, .2fr would only fill 30% of the space while 1fr, 2fr fills the whole thing. (We already have the "pretend there's a transparent 1fr stripe at the end, if they're all fixed" behavior; it can expand to activate whenever the sum is <1, taking the remainder to raise the sum to 1.)

These two fixes would give us a reasonable interpolation behavior, but we'd still have to deal with the mixing of flex/length issue, and define what happened in Grid if you mixed them. (Right now it's a syntax error, so we don't have to deal with it.)

@SebastianZ
Copy link
Contributor Author

Flex isn't interpolable with length in Grid because they cause trigger different layout behaviors, and figuring out a way to mix their effects with continuity would have been way too complicated for the benefit we'd get from it.

Can you elaborate on that? I would have hoped we'd be able to come up with a general solution for this issue. Though I'm currently not deep enough into the layout algorithm differences to make a reasonable point here.

In stripes() flexes are much simpler (they just turn into lengths with a relatively trivial transform), so in theory they could be interpolable. You can't do it naively with a calc(), tho. Flex values are context-dependent, not just on the container size but also on the sum of all flex values in use in the context.

Yes, I didn't explicitly mention that but my examples already take both into consideration.

This means you have two general problems:

1. If the sums of flexes are different between the starting and ending values, the intermediate values will _not_ reproduce what you'd naively expect if you just resolved the flexes to lengths and interpolated them.

2. 0fr has a fundamentally different behavior from any >0fr value.

We could fix both of these! For (1) we could normalize the flex sum to a fixed value (1fr?) at computed-value time. (This is probably a good idea anyway, since the currently-allowed interpolation of flex segments still suffers from this problem - 1fr, 4fr to 400fr, 100fr does not produce equal-sized stripes in the middle.)

Thanks for pointing that out! Normalizing them sounds reasonable to me. Though I wonder whether there might be use cases in which authors want to mix different sums of flex values to achieve a certain effect. If we normalize the sums, this isn't possible, but if we don't normalize, both can be achieved.

For (2) we could reproduce the behavior from flexbox where if the flex sum is <1, it represents only the corresponding portion of the free space, so .1fr, .2fr would only fill 30% of the space while 1fr, 2fr fills the whole thing. (We already have the "pretend there's a transparent 1fr stripe at the end, if they're all fixed" behavior; it can expand to activate whenever the sum is <1, taking the remainder to raise the sum to 1.)

That sounds totally reasonable. Though that's also already defined in the spec. by you and even shown in an example. Or is there still something missing?

These two fixes would give us a reasonable interpolation behavior, but we'd still have to deal with the mixing of flex/length issue, and define what happened in Grid if you mixed them. (Right now it's a syntax error, so we don't have to deal with it.)

It could still be defined as a syntax error in Grid for the time being and be discussed separately if needed. But as I said, ideally we could come up with a general solution for that.

Basically, the algorithm is already defined in a way to make interpolation between flex and other values possible. I.e. first substract any non-flex values from the total width and then split the remaining width according to the sizes of the flex values.
Once that's done, the flex values can be resolved against the non-flex units. (Those non-flex units don't necessarily have to be lengths, btw. Those could be angles as well, for example, or time spans.)

Sebastian

@tabatkins
Copy link
Member

Can you elaborate on that? I would have hoped we'd be able to come up with a general solution for this issue. Though I'm currently not deep enough into the layout algorithm differences to make a reasonable point here.

The Grid layout algorithm invokes different algos if you have flexible lengths vs if you don't. There's not much to elaborate on, it just does different things.

Though I wonder whether there might be use cases in which authors want to mix different sums of flex values to achieve a certain effect. If we normalize the sums, this isn't possible, but if we don't normalize, both can be achieved.

The effect is really weird; if they want to do something weird they can do it by hand by adding more intermediate values. I don't think there's even a moderate use-case for the behavior to be done intentionally.

Though that's also already defined in the spec. by you and even shown in an example. Or is there still something missing?

Ahahaha, no, I just forgot I'd done that. Okay, good.

It could still be defined as a syntax error in Grid

Not... easily. Hm. I suppose I could reuse the machinery that %s do, where it's a syntax error to mix %s with a type if the % doesn't resolve to that type.

@tabatkins tabatkins reopened this Mar 24, 2023
@Loirooriol
Copy link
Contributor

I suppose I could reuse the machinery that %s do

That's precisely what I was thinking: in <length-percentage>, the percentage resolves to a length, so they can be mixed in calc(). That's not the case in <length> | <percentage>.

So we could have <length-percentage-flex> where both percentages and flex resolve to lengths and can be mixed in calc(), and <length-percentage> | <flex> where they can't be mixed.

@Loirooriol
Copy link
Contributor

Interpolation of stripes() is in https://drafts.csswg.org/css-images-4/#interpolating-stripes, so relabeling.

@SebastianZ SebastianZ changed the title [css-backgrounds-4][css-grid] Allow transitions between different types within stripes() [css-images-4][css-grid] Allow transitions between different types within stripes() Apr 1, 2023
@SebastianZ
Copy link
Contributor Author

Interpolation of stripes() is in https://drafts.csswg.org/css-images-4/#interpolating-stripes, so relabeling.

Of course! I am too focused on Backgrounds 4 lately. Sorry!

Sebastian

@SebastianZ
Copy link
Contributor Author

Though I wonder whether there might be use cases in which authors want to mix different sums of flex values to achieve a certain effect. If we normalize the sums, this isn't possible, but if we don't normalize, both can be achieved.

The effect is really weird; if they want to do something weird they can do it by hand by adding more intermediate values. I don't think there's even a moderate use-case for the behavior to be done intentionally.

The example you gave earlier, transitioning from 1fr, 4fr to 400fr, 100fr is also a weird one. Though I tend to agree that having 200.5fr, 52fr when halfway through the transition is probably not what authors would normally expect.

I think it's time to discuss this on a call. The proposed resolution is to adopt Tab's suggestions from this issue and from #8140. They are:

  • Eagerly compute flex values to lengths (avoid transitioning between different flex sums)
  • If flex sum is < 1, it only covers that portion of free space (remainder is pretended to be filled with transparent stripe; same behavior as in flexbox)

I explicitly excluded the issue around mixing flex and length values in Grid, as it seems to deserve a separate discussion.

Sebastian

@Loirooriol
Copy link
Contributor

1fr, 4fr to 400fr, 100fr does not produce equal-sized stripes in the middle

This kinda reminds me of aspect ratios (#4953). If we don't compute fr as lengths, maybe they should interpolate logarithmically? So the half point would be 20fr, 20fr.

@Loirooriol
Copy link
Contributor

Eagerly compute flex values to lengths

If a <length-percentage> stays as-is, I think <flex> should rather compute to a <length-percentage> instead of a <length>.

So 20px, 40%, 1fr, 3fr would compute to 20px, 40%, max(0px, calc(15% - 5px)), max(0px, calc(45% - 15px)).

@tabatkins
Copy link
Member

No need to invoke log transitions to fix the behavior here. We want linear transitions, and that has a straightforward meaning, it's just that the actual flex values used are context-specific (they depend on the sum of flexes around them). Normalizing a >1 sum to =1 fixes that issue so flexes on either side of the transition can be directly compared/transitioned sensibly, and it basically matches how we deal with flexes elsewhere.

@tabatkins
Copy link
Member

tabatkins commented Oct 11, 2023

If a <length-percentage> stays as-is, I think <flex> should rather compute to a <length-percentage> instead of a <length>.

I'm not sure how I missed this comment, but yes, that makes perfect sense, @Loirooriol. Each flex computes to max(0, 100% - sum-of-percents - sum-of-fixed) * flex value / max(1, sum-of-flexes), and now it's a perfectly ordinary length-percentage with reasonable linear transitioning behavior.

@fantasai

This comment was marked as resolved.

@tabatkins

This comment was marked as resolved.

@w3c w3c deleted a comment from css-meeting-bot Dec 13, 2023
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-images-4][css-grid] Allow transitions between different types within `stripes()` , and agreed to the following:

  • RESOLVED: fr values in stripes() convert to max(0, 100% - sum-of-percents - sum-of-fixed) * flex value / max(1, sum-of-flexes) (a <length-percentage>) when needed for interpolation
The full IRC log of that discussion <fantasai> -> https://github.com//issues/8622#issuecomment-1758136484
<fantasai> TabAtkins: Currently, in stripes() you can write widths as length, percentage, or flex
<fantasai> ... we didn't have interpolation behavior for between flex and lengths
<fantasai> ... but in this context, the flex definition is very straightforward and can be expressed in terms of percentages and lengths
<fantasai> ... so my proposal was that each flex in a stripes function will compute to a particular expression
<fantasai> ... which makes it into a <length-percentage>
<TabAtkins> max(0, 100% - sum-of-percents - sum-of-fixed) * flex value / max(1, sum-of-flexes)
<fantasai> ... and then you can transition that with all the other length-percentages just fine
<fantasai> fantasai: Sounds fine
<Rossen_> fantasai: makes sense. Do we store as computed value?
<fantasai> ... or do we compute through to <length-percentage> only when interpolating
<fantasai> TabAtkins: It's best for authors to leave fr's as they are, unless they need to convert for an interpolation
<flackr> +1
<fantasai> +1
<fantasai> s/store as/store fr as/
<fantasai> TabAtkins: [explains the formula]
<fantasai> flackr: I like converting just-in-time
<fantasai> ... we do this with other types when interpolating
<fantasai> RESOLVED: fr values in stripes() convert to max(0, 100% - sum-of-percents - sum-of-fixed) * flex value / max(1, sum-of-flexes) (a <length-percentage>) when needed for interpolation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Unslotted
5 participants