-
Notifications
You must be signed in to change notification settings - Fork 711
[css-values-5] Maybe min, max and step should not be part of the random-caching-key #11742
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
Comments
The issue is that if min/max/step aren't included, then it exposes details of exactly how you generate the random value. To avoid that, I'd have to specify that the random value is, specifically, a 0-1 real, and then specify exactly how it's turned into the random value. (aka, scaled to the desired range, or converted to a 0-N integer for the step) I definitely could do that, and it would avoid some other oddities (like |
I think the more obvious way to do this would be to use the same
You can do the same thing for this: pull out the I think these use cases are relatively rare, and it's more obvious what you're doing if you synchronize the I think what's more likely, actually, is that you want two
But this will generate squares right now, right? It seems to me that requiring a shared name to tie them together makes more sense than requiring different names to differentiate. As you say, if you accidentally end up with two The important thing is to make sure that |
That's how my brain expected it to work. But maybe that's programmer brain. |
See also #11747, which would be resolved in the min/max/step were not part of the random key. |
These arguments are pretty convincing, and being able to knock out #11747 would be nice. However...
The issue with making a "default" random() (without an explicit caching key) assume a unique key is that it's not very clear to authors when such a key would be generated. For example: .rectangle {
/* these would be different random values */
width: random(10px, 100px);
height: random(10px, 100px);
}
.rectangle-2 {
/* these would be different random values */
--size: random(10px, 100px);
width: var(--size);
height: var(--size);
}
@property --size2 { syntax: "<length>"; inherits: true; initial-value: 0px; }
.square {
/* these would be the *same* random value, making squares */
--size2: random(10px, 100px);
width: var(--size);
height: var(--size);
} To an extent authors already have this confusion, since This is how I arrived at the current caching behavior - it can result in accidentally linked random values sometimes, but it also ensures that authors don't have to care about when the substitution occurs. So, hm, tradeoffs either way. Maybe it is better to force authors to think about the variable resolution time in return for having "more random" behavior by default and avoiding the value-resolution dependence. |
Ah, right, the other reason I made the current behavior is it reduces the coupling to the precise source document. That is, currently, you can have multiple rules applying the same declaration to an element, and it's the exact same behavior as only having one of them apply. With "implicit unique key", you lose this: having multiple stylesheets applying the same styles to an element will give different results. This exposes things like stylesheet deduping, which currently are undetectable from inside the page. It also exposes the difference between applying a style to multiple elements via a selector and via The current behavior instead makes the caching key based purely on the source text itself, not its location, so you can't tell whether the same value is applied from several rules or just one, or if a repeated stylesheet is duped or deduped, etc. The accidental linking of functions that happen to have the same arguments is just an unfortunate side effect. I included as much data as possible in the key precisely to make this less likely to happen by accident. |
So just to distill this down, my pushback here is that, today, for all CSS values, the precise source location of a value doesn't matter. (Some things care about which tree context a value's stylesheet is in, but that's the extent of it.) So Having The only way to avoid this is to make the
|
Definitely Randomness needs to resolve at computed-value time. That's required for it to inherit sensibly. Given that, I think each Then if the author wants to tie things together, they can do that explicitly. Two Yes, that means registered custom properties behave differently because they compute things up front. But that's already true, as you point out. The one other thing to be careful of is shorthand properties and omitted values. We probably want |
So I think you are saying the default key should be a tuple of
|
This is still hiding the complexity. What, precisely, is the scope of the uniqueness? We need the ID to be stable across styling passes, and across the same style applied by different rules, but we also need it to be different across... some boundary. At minimum, between properties. We probably want multiple uses in the same property to be unrelated, too. Ah, now that I'm trying to find some text I remembered seeing, I notice you were more specific in the original version of your comment in my email, but editted it to be more generic later. I think your original suggestion is indeed exactly what we'd have to do: construct a key from the property it's used in (recording the shorthand used, before expansion into longhands) + an index for how many random functions precede it in the value. (+ some extra entropy for the pageload itself, so different pages or different visits to the same page are unrelated, but that's already baked into the definition and applies even when a key is given) It would be interesting if we could also bake in some entropy from the token stream itself, so using the same number of random()s on the same property, but changing the rest of the property value in some way, would also result in different values. However, that requires a very precise canonicalization of the value into some sort of bytes, and I don't know that it's worthwhile to do so. It should probably be fine to just stick with property+index; you usually won't notice the connection. |
Note that this does still mean that, if you don't use
...unless we specify that it's literally equivalent to specifying |
Closely related issue: I'm trying to make sure that custom properties work in an understandable way, both registered and unregistered. Unregistered is easy, they're just a little weird. They don't even recognize that a random() function exists, so everything about them waits until they're actually substituted - both computation of the default caching key, and resolving the per-element value. A little tricky, but explainable. Registered properties present an issue, however. They'll see the random(), so they can compute the default caching key based on the custom property. And if the computation is fully resolveable by computed-value time, they can even handle I could hand-wave some hidden state that has to be carried with it, but I'd prefer avoiding that if at all possible. This suggests that the "random base value" that is computed at computed-value time needs to be explicitly exposed, so that the computed value of a random() or random-item() function is some other, non-random function, with the random value subbed in. That is, at computed-value time:
We then rely on normal computed-value behavior from there, simplifying if possible. This would result in an observable |
Uggghhhhh, no, my second bullet doesn't work, because the R's range depends on actually evaluating A, B, and C. I need to phrase it in a way that lets me keep them unresolved. Unfortunately, even if I'm more explicit about the random int calculation, it doesn't let me switch into the "infinite steps" error case; the calc() would instead hit some infinity degeneracies. Oof, and the first bullet point is bitten by that too - the That is, |
Hm, just spitballing if we could handle this via It would need the @function --step-range-limit(--a, --b, --c) {
--dist: calc(var(--b) - var(--a));
--step: calc(var(--dist) / var(--c));
--rounded-dist: round(down, var(--dist), var(--step));
result: calc(var(--a) + var(--rounded-dist));
}
@function --step-count(--a, --b, --c) {
--dist: calc(var(--b) - var(--a));
--step: calc(var(--dist) / var(--c));
result: calc(1 + round(down, var(--dist) / var(--step), 1));
}
/* then `random(A, B, by C)` computes to... */
calc-range(R,
0: A,
by steps(--step-count(A, B, C), jump-none),
1: --step-range-limit(A, B, C)
) Tho, no, sigh, that still doesn't get me some of the argument handling. I guess I will just need to make |
Heh, amazing. Reverted to an earlier version. |
No they won't, because we're also including the element identity by default in the caching key. |
No, I don't think these identifiers should be author-exposed. If the author wants to tie things together, they should do it explicitly. |
Like I said, the computed value of a |
I don't see this keyword as helpful. We either just allow the number directly, we compute this to a (Tangential point: we should make sure |
Ah, I missed that, ok. Sounds reasonable.
Now that I've hit on the "just put the random number right in the function" solution, it's okay to make this value not author-exposed, so I agree.
That's not the issue, it's that a registered custom property (and any normal property containing a % that requires layout) containing So we really do need to guarantee that we resolve the value in some way by computed value time.
Nah, random() is much closer in spirit to, say, |
So @tabatkins and I worked through this issue and our proposal is the following:
Example of the new caching rules outline above:
We think this resolves all the comments and suggestions in this issue and in #11747. Agenda+ to discuss; edits in-flight to the spec for review. |
Edits checked in! |
The
|
The Perhaps it needs to be named I'd also be fine with @Loirooriol's approach. |
I inverted the keyword for two reasons:
In both cases it causes the style to match between some context-specific notion of elements - in this case, it causes elements to match their element-specific IDs together (setting them all to null, but that is indeed matching). It's also closely related in semantics to the I'm open to other names, as we said in the comment, but it needs to be something that clearly expresses the behavior to the author, rather than something that's meaningful only with respect to the internal mechanics of the caching key. |
Some ideas from AI that I somewhat liked: |
I don't follow what auto name you are talking about. Do you mean Anyways, it's actually more than 2x2 because of indexes. Are the grow and shrink ratios set to the same amount? flex: random(match-element, 0, 1) random(match-element, 0, 1); I guess they don't, in order to maximize randomness, and because (I believe?) you can specify a common ident if you want the same amount.
So even though omitting the options implies the "weird curve" in the original-based approach, I think it's preferable because it directly maps to "varies with". The inverted option kinda maps to "shared with", but it's less straightforward, because it's not obvious that an ident shares among properties and indices but not among elements. |
The keyword can't mention selectors, as it's not about that - completely different style rules can still share the same random base. And because there's already another notion of "matching" being employed (by name), I think this probably has to include "element" in it to indicate that's the axis of matching. Thus the current name There's another complicating issue here - if we went with a Hm, intentionally breaking that connection by starting with the noun might be workable, then. |
The caching key has two pieces - a "name" and a nullable element ID. If you don't provide an ident, the name is auto-generated.
No, that's the "auto name" I was referring to. Property+index forms the name portion of the key, together. I don't think it's useful to think about property and index separately; neither the current nor previous text treated those as distinct things. The old text didn't pay attention to either; the new text pays attention to both.
Correct. The two instances have different auto names, so they end up with different values. You can manually specify a shared name if you do want to tie them together.
Specifying an ident almost always reduces randomness; it's specifying a "shared with" semantic. (The only time it can actually increase randomness is if you specify different idents between two style rules setting the same property; they'd otherwise generate the same auto name and thus share values.) So it matches with the behavior of the keyword; they both increase sharing. In the previous formulation both the keyword and the ident reduced sharing. Previously, omitting an ident just used a "blank" ident, so every instance that didn't specify a name was implicitly shared with every other unspecified name. So specifying a name reduced sharing, and the keyword reduced sharing.
Your final "old" example is wrong. Overall you seem to be really overcomplicating this, tho. ^_^ The name indicates how things are shared; you can choose to reuse a name across properties, across indexes, or across any other dimension. There's no need to think about those dimensions separately. |
Back to keywords: how about we just go simple and use Edit: No, nevermind, brain fart. While |
It's the "weird curve" thing. Which sure isn't great, but I'm not convinced that the alternative is any better.
But not "across elements" dimension. Doesn't seem obvious to me. I think it might be beneficial if some CSSWG member who has lots of followers on Twitter (or BlueSky or whatever people use nowadays) creates a poll asking developers what behavior they would expect. |
I'm not sure what you're talking about here. In the old version, |
@Loirooriol in #11742 (comment)
@tabatkins in #11742 (comment)
So yeah they look like opposites but "completely random" is desirable in a short way, and "completely shared" can be opted in with just It's a matter of whether it's more confusing to have a special behavior for no options which is the opposite of what you would normally expect, vs the inverted model where a keyword implies sharing across all dimensions except (for no immediately obvious reason) across elements. |
I still don't understand what you're saying. The "original" cell in your last row is wrong. If by "original" what you mean is "something new that I'm making up, that resembles the original design", you should say that instead. |
Elements are never a dimension addressable from style (only from selectors, with careful coordination with your HTML) So if we want to be able to distinguish "shared between elements" and "unique per element" it needs a special-purpose keyword, not something controlled by the author. When element identity is potentially significant in other properties, we similarly handle it with special-case behavior. And, since we're going with Having the default behavior be "unique per element" and the keyword being "unique per element" is inconsistent. It means leaving off the keyword has opposite behavior depending on other values - It also means there's no way to say "automatically vary by property/index, but match between elements", unless we add more syntax to let you manually opt into that behavior. In your table you glossed over this by instead manually using idents to recreate the behavior, but the point is that there's a useful automatic behavior you get by omitting the ident, and you should be able to get that regardless of the independent choice of unique/shared by element.
It is almost always the case that omitting something acts consistently regardless of what other values you specify or omit; if we have any exceptions they're super special cases. This extends all the way to "omit everything" when that's possible - this almost always gives the same behavior, for each omittable thing, as omitting it. Breaking that pattern should only be done with a very good reason. So yeah, it's way more confusing to have a special beahvior for "no options" which is the opposite of what you'd normally expect. |
The CSS Working Group just discussed The full IRC log of that discussion<fantasai> Proposal at https://github.com//issues/11742#issuecomment-2707697957<emilio> TabAtkins: defining randomness in CSS because the execution model is not strictly temporal <emilio> ... can't hold on to existing values <emilio> ... don't want to calculate a random value on every recalc <emilio> ... you need to have some stability <emilio> ... the old draft did this via a caching key approach <emilio> ... so if the key is the same between two instances of a random() function then the values need to be the same <emilio> ... this caching key has changed tho <emilio> ... author could provide a custom-ident, but also min/max/step values were pulled in <emilio> ... any other random function would probably have a different step and thus generate a different random value <emilio> ... this worked reasonably well but did mean that values could be accidentally linked together <emilio> ... so random width/height you would get a random square <emilio> ... rather than a random value <fantasai> s/value/rectangle <emilio> ... fantasai proposed something else, which is on the spec right now. Caching key is (property, index among random values in that property) <emilio> ... so if you use `width: ...;` the key is (width, 0) <emilio> fantasai: you don't want a global random <emilio> ... caching key also includes the element identity <emilio> ... all of that gets computed to a random value which they inherits so that we don't recalc it for inheritable properties <emilio> TabAtkins: so on a single element if you use the same values on different properties you're guaranteed to get distinct values <emilio> ... you can override it if you want, so you could still pass the same ident to width/height <emilio> ... some good examples of this are in the spec <emilio> ... don't know what people might need for this but I propose we accept the new draft based on elika's proposal for the different key <emilio> q+ <emilio> ... one further question about bikeshedding the keyword name <weinig> q+ <astearns> ack emilio <fantasai> emilio: Is this declaration index thing local to each element you compute? <fantasai> TabAtkins: There's a 2nd thing you can provide, which is a keyword indicating whether this value should be shared by all elements with this style, or be element-specific. <fantasai> TabAtkins: Then it either includes the element identity in the caching key or not <weinig> q- <fantasai> emilio: Say you have 2 selectors with random() and you have an element that matches one, and another that matches both of them <fantasai> emilio: so in one the index ... <fantasai> TabAtkins: index is just the number of random() instances in that declaration. <fantasai> TabAtkins: so for 'width' always index of zero <fantasai> emilio: So if they're in different elements, they would be shared? <fantasai> TabAtkins: if not adding extra "match-across-elements" keyword, then they all include element ident <fantasai> TabAtkins: This is new, because CSS generally doesn't care where a value came from <fantasai> TabAtkins: whether spell out in comma-separated selector list, or in a style attr, these are all the same <fantasai> TabAtkins: I want to maintain that equivalence as much as possible <fantasai> TabAtkins: so only including information from declaration it lives in. Nothing from style rule or selector. <fantasai> emilio: So maybe some things that you would expect to work don't? <fantasai> emilio: e.g. 10 elements, each with style blah: random() <fantasai> emilio: ok, sounds good <fantasai> TabAtkins: To be clear, having it vary by element is the default. You have to specify keyword to make it shared across elements. <fantasai> TabAtkins: so common case would be what you expect there <fantasai> emilio: seems reasonable <fantasai> emilio: Shorthands maybe funny? <fantasai> TabAtkins: it uses the declared property, e.g. in 'margin' you'd use 'margin' as part of the key, not the longhand names <fantasai> TabAtkins: if you write 'margin: random(...)' you get equal margins on all four sides <fantasai> TabAtkins: there's a bit of text in the draft about how this works for custom properties, it's a bit weird (unfortunately) <fantasai> TabAtkins: unregistered vs registered properties, since the latter compute and the former don't before substitution <fantasai> emilio: Might be confusing, but as long as we clarify impact of registration should be ok <fantasai> astearns: over time <astearns> ack fantasai <emilio> fantasai: what we're trying to do is by default you get max randomness (varies by element, property, declaration index), but doesn't by where you declare it <emilio> ... within an element you can make it shared by using the custom property <emilio> ... can share across elements with the keyword tbd <emilio> ... I think it's the right direction <emilio> astearns: a bit concerned about oriol's comments <fantasai> astearns: Concerned about Oriol not being convinced yet. <emilio> TabAtkins: I feel strongly about this model <emilio> fantasai: we should give oriol a chance to comment on here <emilio> astearns: let's defer this to next week |
During the discussion I was mulling over how the caching rules would interact with animations. I guess as written, if But it made me wonder if there's an additional dimension authors might want: per-iteration randomness. For example, I might implement a randomly-bouncing-around animation like this: @keyframes bounce {
0% { left: 0px; top: random(match-element, 0px, 1000px); }
25% { left: random(match-element, 0px, 1000px); top: 1000px; }
50% { left: 1000px; top: random(match-element, 0px, 1000px); }
75% { left: random(match-element, 0px, 1000px); top: 0px; }
} Where I want lots of elements to follow the same randomized path, maybe with different timing offsets so they "follow" each other, but I also want different random values drawn each time the animation cycles. |
As an author, I prefer more randomness as the default, with options to opt-into shared values - so I really like the change from Without referencing spec lingo, I don't see I also love @kbabbitt's per-iteration use-case. I would use per-iteration behavior with or without element sharing. I'm not sure I've entirely tracked if either of those is already possible in the proposal. |
@kbabbitt With the current proposal you should be able to use the custom ident for that:
|
Sorry if this was discussed already, but I was wondering about a case where one toggles some class with randomness on an element
It sounded like this would only pick one random value and then persist that for all other uses of the I wonder if there is an opportunity to include some sort of a generation id in the cache key that updates if the style applicability is interrupted as in the case above. There is somewhat tangential precedent to this in contain-intrinsic-size's last remembered size and how that sticks around while the property is continually applied and is forgotten otherwise |
@nt1m as I understood the proposal, that would select a random value once for each keyframe, and then reuse those same values for every iteration of the animation. Is that not the case? The use case I suggested called for a new random value, for every keyframe, for every iteration, but each time a random value is drawn, all elements to which the animation is applied get the same value. |
So after a week I'm getting more used to the new proposal. Not opposed, but still a bit concerned it may the a source of confusion if authors assume that |
@Loirooriol I agree that might be something authors assume at first, but I'm not sure that's enough reason to make it the default? If the keyword we land on is clear enough (reading back, I like |
Re: per iteration randomness, I agree that using the custom-ident option is the right answer here. If we tried to randomize per keyframe, as in Kevin's example, then it brings in the same issues/confusion that randomizing based on style rule would. Is there a difference between That said, if it is something we do end up wanting to support more automatically, I think the right answer is that the auto-generated key would, in addition to the property/index, incorporate the keyframes name and keyframe index. That would mean that each instance of
Correct, that would be the same random value every time the class applied. I'm pretty opposed to a "generation key" for this, as it would make things distinct in unexpected cases, like If you're using scripting and want random values, you can always supply them yourself, at exactly the sharing granularity you desire. |
The CSS Working Group just discussed
The full IRC log of that discussion<dholbert> ScribeNick+ dholbert<fantasai> scribe+ <fantasai> TabAtkins: The random() functions currently, by default, are maximally random. <fantasai> TabAtkins: two axes controlling how they resolve <fantasai> TabAtkins: 1. Which property they're in, and what index in the declaration <fantasai> TabAtkins: 2. whether same across all elements or different <fantasai> TabAtkins: can change either of these <fantasai> TabAtkins: Force same across properties/instances by applying custom ident <fantasai> TabAtkins: or force same across elements by using special keyword <fantasai> TabAtkins: some opposition from oriol about the way that the element sharing was phrased <fantasai> TabAtkins: He preferred previous model where elements shared by default, and opt out <fantasai> TabAtkins: Since then several people commented preferring that starting with maximal randomnes is better <oriol> q+ <astearns> ack oriol <fantasai> TabAtkins: so my proposal is to adopt the proposed model, and then address naming quesiton <fantasai> oriol: My concern is that when you provide the keyword, not clear that you would share across instances but not across elements <fantasai> oriol: In our other properties with custom elements, you get same behavior for same custom ident <fantasai> oriol: I worry it could create some confusion <fantasai> oriol: But I think if you provide random() with no parameter, maximizing randomness is better <astearns> ack fantasai <TabAtkins> fantasai: one option is that "no parameters" is maximum randomness, using a custom ident gives you global correspondence, and using custom-ident+a keyword gives you shared in an element, but differetn across elements <fantasai> TabAtkins: Yes, that would be a "per-element" keyword, discussed already <fantasai> TabAtkins: I don't like that because it breaks independence of omitted chunks <fantasai> TabAtkins: omitting all params shouldn't be different from the behavior of omitting each individually <fantasai> TabAtkins: We usually adhere to that model, so I prefer to stick to that model. <fantasai> TabAtkins: Specifying peels back specific aspect of randomness. <fantasai> TabAtkins: rather than doing an inversion as soon as you specify anything <fantasai> fantasai: I don't mind either way, just saying this is another possible syntax model. <fantasai> astearns: so should we resolve on this model of maximal randomness? <fantasai> RESOLVED: Accept the proposal for changing random caching as stated in the issue. <fantasai> TabAtkins: We need to name the share-randomness-across-elements keyword <fantasai> TabAtkins: some options were `element-shared` and `all-elements` ... other suggestions in issue <fantasai> Proposal is at https://github.com//issues/11742#issuecomment-2707697957 <fantasai> [this should go into the resolution] <fantasai> astearns: Do we have existing keywords related to this idea? <bramus> q+ <fantasai> Suggested keywords https://github.com//issues/11742#issuecomment-2708387121 <astearns> ack bramus <astearns> ack fantasai <bkardell_> +1 <TabAtkins> fantasai: I think I agree with tim on match-element being confusing, it doesn't really convey... we're not matching this value to this element, liek it does in VT <TabAtkins> fantasai: it's "this function should match across all elements", different concept <bkardell_> thisfunctionshouldmatchacrossallelements <TabAtkins> astearns: should we resolve on 'element-shared'? <TabAtkins> fantasai: element-shared sounds weird <bkardell_> I think I disagree all elements sounds more obv <TabAtkins> straw poll: 1) element-shared, 2) all-elements, 3) both of these suck, i have a better idea <TabAtkins> 1 <schenney> 1 <ydaniv> just "shared"? <astearns> 1 <bkardell_> 1 <Kurt> 1 <fantasai> 2, but I wish I could pick 3 <miriam> 2 <alisonmaher> 1 <bramus> 1 <ydaniv> 1 <oriol> Maybe 3) shared-across-elements? Or 2) <vmpstr> (abstain) <kizu> 2 <dbaron> 1 (weakly) <dbaron> (what about across-elements?) <fantasai> +1 dbaron <oriol> Not clear if across-elements is varying across elements or caching across elements <kbabbitt> match-across-elements ? <fantasai> fantasai: match-elements ? <fantasai> TabAtkins: too close to v-t-n: match-element <fantasai> ydaniv: by-property? <dholbert> (I'm not particularly enthusiastic about either of the options but I don't have a better suggestion ) <emilio> (same as dholbert) <fantasai> PROPOSED: Go with element-shared for now, keep renaming issue open because we're all unhappy with it <fantasai> RESOLVED: Go with element-shared for now, keep renaming issue open because many people are unhappy with it |
I also find I read it as something that causes the random base value to be specific or matched to one unique element. Maybe because Is it clearer by using a plural? e.g. |
Currently, CSS Values 5 says that the min, max and step are part of the
random-caching-key
.However, this prevents some possibly useful techniques where an element shares the same random value, but with different min, max and step with another property on the same element, or with another element.
Conceptually, I would expect that the underlying 0-1 random number is sampled based only on the
dashed-ident
orper-element
keyword, and then min, max and step are just math applied to that underlying value.Consider
I want this box to have a random size, but always a 2/1 aspect ratio. With the current spec, width and height get their own random values.
Another example might be that you have two elements that want a width based on a single random value, but they quantize that value in different ways.
The text was updated successfully, but these errors were encountered: