-
Notifications
You must be signed in to change notification settings - Fork 677
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-values] random() function #2826
Comments
I don't think random() would make sense. When should it be evaluated? Whenever the style of an element is resolved? That's right now implementation dependent, there are engines that optimize style changes better than others. |
In my previous musings on the topic, I stated that there are two ways we can evaluate random(), both of which are useful:
The first means that There's possibly a third, even less stable mode: evaluated freshly at "application" time, whenever the rule would trigger a transition due to it winning the cascade over a different specified value. This is different per element, and is different each time you hover the element. We'd also want a few different types of randomness; numeric ranges are okay, but getting a random value from a list is very useful and handles colors well, too. |
@tabatkins I'm definitely in favor of the third option. Would you mind clarifying what you mean by "getting a random value from a list" though? |
@bendc Probably like I’d say also provide an option to distinguish between int / float random. |
This is not deterministic, right? |
What do you mean by that? |
That the time you apply properties as a result of a style change is not defined, in the sense that engines can do wasted work for that in different ways. For example, if you have |
That's why I didn't base it on style application, which is indeed undefined, but based it on transition triggering, which is. (The timing is still a little undefined, but assuming you can approach steady state, whether or not a transition occurs is well-defined.) |
What do you mean with 'transition triggering'? You mean only resolve |
If a random()-containing declaration would newly apply (such that a transition on the property would trigger, if you treated the random()-containing declaration as being a different computed value from anything but itself), then at that point you resolve the random() to a fresh random value and apply it. |
What happens if the OM touches that declaration? Should we re-compute random() to elements that already match it? Such a model sounds complicated to me. |
I think the most logical option from a usage standpoint would be to calculate the random number when a rule is applied to an element. It should only be recalculated if that rule has been removed and then added again (class addition/removal, hover triggered etc). That would allow for the random value to persist and update controllably rather than in some undefined fashion whenever the browser feels like it and would mean multiple elements targeted by the same selector would receive a different random value. I'm not sure how easy it would be for a browser to implement though. |
It seems like authors might want different things:
Rather than invent different random functions, I think this functionality is better served with CSS variables, and the ability to set those from script. |
The issue with that approach is having to generate a custom property for every element you are targeting. At that point you may as well just style the elements directly with JS. A CSS solution is much more convenient. I believe it would be worth developing some use cases to determine the requirements. |
(Note: significant reasoning and discussion here; simple proposal sketch in the next post if you want to just skip to that.) Yeah, just using script-set variables only solves the "randomly determined at parse time" case. It doesn't help if you want to get a different random value per element, unless you go significantly greater lengths. Dealing with random value "persistence" is the hard problem to solve here. We need to define precisely what data gets fed into the random generator; or more simply, what data we use to cache the random values, so we can retrieve the same value when we reevaluate style. That gets us to the rub; how are you caching the values when you have a rule like .foo {
color: rgb(rand-int(0, 255) rand-int(0, 255) rand-int(0, 255));
} The three rand-int calls all need to give distinct values, but need to be persistent across style recalcs. Let's also assume these are meant to be different values on each element, so you can't just do parse-time resolution and call it a day; the functions have to persist until probably computed-value time, at which point they turn into an integer. Obviously, the element itself is part of the cache key. What else? Can't just use property; there are three instances and they need to all be distinct. Maybe property + occurrence count (first, second, third)? That still means that different rules would reuse cached values, which would probably be unexpected: a hover rule setting it to Maybe key it off of rule as well, but we don't have a good, stable notion of rule identity. Changes to a stylesheet can cause a reparse and create totally new objects in the CSSOM, so using OM object-identity is probably bad. Dont' want to, like, hash the rule contents either, as it would mean that changing other properties would alter the random value. Ultimately, I think the only reasonable cache key, that works reliably and with a minimum of surprise, is an author-provided custom identifier. As long as you provide the same identifier, the random() function should resolve to the same value. One more wrinkle, then: we don't want to expose internal details of the random data, to allow implementations flexibility to change and be different from each other. Thus, we don't want to reuse the same random number for different random functions; the obvious trivial implementation of rand-int() would just modulo a random 32-bit int or something, and using the same value for 1-3 as 1-5 will expose information about the number being used (its value modulo 3 and modulo 5). So we also need to take the range start and end as cache keys. So: the random values are generated on demand, and cached against:
This, in addition to being consistent and understandable, has the additional benefit that you can jam a random() into a custom property, and it'll Just Work© without you having to think about it; each use of the var() will get the same value. None of the other possible solutions do this, afaict. Now, output spaces. I think it's valuable to have both random integers and random numerics (reals, lengths, angles, etc). Just using a random real in a place that expects integers will not work as expected: rand-real(1, 3) will generate a number that rounds to 1 25% of the time, that rounds to 2 50% of the time, and that rounds to 3 25% of the time, a far cry from the 33% you'd like each integer to be generated. You need a floor() function to write a rand-int() out of rand-real(), and it's non obvious how to do so; I write I don't think we need integer-valued dimensions; that's rarely, if ever, actually useful (as opposed to rounding to larger integers, like a random multiple of 100px), and you can just use The real-valued function should accept any numeric value as its start/end, and just require that their types match. (Specifically, that adding the two types is successful, with a null percent hint.) Lists of values (like, grab a random color from these possibilities) is a valid use-case, but I don't want to solve it directly via a different random function. I think a reasonable use-case is to have sets of values that are valid to use together, but that you want a random choice of. As such, I think we should add an |
So, proposal:
Like calc(), the rand-* functions evaluates at computed-value time if possible (if their arguments can be resolved at computed-value time), and at used-value time otherwise. They resolve to either a random integer between the first and second value (inclusive at both ends, so For either function, you must supply a If you additionally pass the
The UA maintains a cache of random values, keyed by a tuple of:
Whenever the UA wants to resolve a rand-* function, it grabs a value from the cache if it exists; otherwise generate a fresh random value and put it in the cache. Then it turns that random value into a value in the appropriate range, via whatever means you want. (More than likely, a simple modulo and addition for integers, and a rescale and addition for reals.) The |
What would happen in case of unclear integers, e. g. Could this be solved with counters instead, which are always integers? |
Thanks a lot for following up on this thread, super excited to see this! Before I comment on the proposals above, I just wanted to make sure we're philosophically on board with the idea of extending the capabilities of CSS as such. I guess it's hard to find an argument against it since we already have Is that a problem per se? I'm not sure, but I'd rather make sure before we jump on the implementation details :) |
rand-int() is integers only, not dimensions. But for rand-val(), it's not resolved until computed or used value time, so the start and end points are already fully absolutized, and thus totally clear.
Counters are... not something we want to build other features on top of. They have a lot of strange quirks.
I am, but I'm utopian about these things. ^_^ I've been thinking about randomness in CSS for many years. At bare minimum, exploring this space will serve as a great case-study for what we need to be sure that we expose for Houdini Custom Functions; I want to ensure that authors could create a |
@emilio was concerned about the design implying a global hash of ident=>random state, as it would imply trouble with parallelizing. This shouldn't be the case; the "random()" function doesn't actually need to invoke a random generator (and, as far as I can tell, can't do so in any reasonable capacity). Instead, it's a hash function + mapping the result into the specified numeric range, relying on the following information only, all of which should be parallelism-friendly afaict:
Concerns, @emilio ? |
Not particularly, I guess. |
@bendc Yes and you should keep using Math.random() for this. It is not CSS's role to do compute. Please don't mix things up. HTML is for markup. Each have their reasons to be, if you need compute, use JavaScript. |
I've been looking for something like <div>I use random from CSS</div>
<div style="--random-bg: 360;">I use random from DOM</div>
<div class=load>I am randomized once per load event</div>
<div class=click>I am randomized once per click event</div>
<style>
div {
--random-bg: 360;
background-color: hsl(calc(var(--random-bg) * 1deg), 75%, 50%);
}
.load {
--randomized-on-load: 360;
background-color: hsl(calc(var(--randomized-on-load) * 1deg), 75%, 50%);
}
.click {
--randomized-on-click: 360;
background-color: hsl(calc(var(--randomized-on-click) * 1deg), 75%, 50%);
}
</style>
<script type=module>
import computedVariables from 'https://unpkg.com/computed-variables/index.es.js'
computedVariables(
'--random-',
value => Math.random() * value,
window,
['load']
)
computedVariables(
'--randomized-on-load',
value => Math.random() * value,
window,
['load']
)
computedVariables(
'--randomized-on-click',
value => Math.random() * value,
window,
['click']
)
</script> You can create a CSS variable like
It seems like everything people want is possible already! In the past I'd have said that this was a much-needed feature that should be added to CSS for animation and other things, but considering how flexible and powerful it is to use JavaScript's own It's also easy to create random choice function in JavaScript that can pick randomly from a list of values given in CSS too, like |
@tomhodgins Thank you for your comment, I'm not necessarily an experienced web developer and was pointing this out solely from a software design perspective. I am glad it makes sense to web developers like you too. |
Being able to have control over the randomization is important. @tabatkins raises all the scenarios, and the discussion of using variables from @smfr and @tomhodgins is important, but i feel being independent from scripting is necessary. Anyways, i did some work on those ideas with AliceJS a while back: http://blackberry.github.io/Alice. Also a sample demo at http://blackberry.github.io/Alice/demos/fx/bounce.html. |
@ldhasson Problem is, if you start with this, you're going to implement another JavaScript inside CSS. |
Since frameworks don't really give developers full control over referential equality of elements, I imagine the custom ident method will be more reliable. Does anything prevent |
While I know that React/etc usage is very common, there's plenty of HTML written without it. ^_^
Nope, as it's just a keyword in the function, you can use vars to substitute it in as normal. |
Of course, I was just thinking of the kind of guidance we'd give to developers. |
@tabatkins Is there a copy of the WIP CSS-Values-5 spec somewhere that I can use to submit it as an entry for Interop 2023? Based on that conversation, would we also need to include the upgrades to "calc() per values-4" per your comment in the CSSWG IRC log? Thanks! |
Having a per-element value might be generally useful (eg #8320), so I'd encourage using something like |
@jakearchibald @tabatkins That also begs the question whether I think the latter would be the most sensible, personally. In that case, if we did implement something like |
Good point, it should def be unique per pseudo as well. |
First draft up at https://w3c.github.io/csswg-drafts/css-values-5/#randomness |
Instead of |
@smfr I think this spec was discussed previously:
I agree that it would still be worthwhile to introduce a way to select an item from a list, whether via
The benefits include:
|
@tabatkins One question with either approach (from re my previous comment)— if someone wants to populate some items into their list that are contained in a variable, is there a way to spread them into the For example, how would this be treated: * {
--multiple-items: 1, 2, 3, 4, 5;
--single-item: 6;
some-prop: random-item(var(--multiple-items), var(--multiple-items));
} Would this return either ( |
As we discussed at the f2f when this was brought up, doing it like (We still want to do
Assuming you meant The items have to be semicolon-separated, so with the way things are specified right now it's actually not possible to put several items into a single variable. (Both custom properties and the fallback of var() use |
Instead of separate People would then probably also want |
What are the use cases for CSS-only random numbers? |
Here's an exploration I did of "random" effects with the paint API. https://jakearchibald.com/2020/css-paint-predictably-random/ |
Common use-cases for [pseudo-]randomness in CSS is for creating a visual variance between elements. Right now the most common way to achieve this is through “cicada principle” technique and adjacent methods (original article, article by @LeaVerou), but it can be very tricky to set up. Actual usage from the top of my head (encountered in the last few months and remembered about them):
|
Been using a RNG with a However I don't think the use-cases for paint worklets are the same as for managing random effects across "collection" of items (like others have suggested here). The way I mostly imagine using random is for
or even a color lerp
While
|
@ShayDavidson I've specifically been considering the use case of a seed PLUS an index/nth value for unique values per sibling as well. The #some-id :nth-child(odd) {
--ident: [static-text] sibling-index();
--random-value: random(--ident, 0px, 100px, by 1);
width: var(--random-value);
} Here, we are building the custom ident using any arbitrary ident-like string as well as another type such as the
Hypothetically, you can use as many parts inside one of these idents as you like, similar to how in some keyed blocks in JS, you can build the key like this:
|
Check out the "Blinking Switchboard" part of this post about the recent Next.js website: https://rauno.me/craft/nextjs Seems to me randomization of which dots animate and how much and how long would make for a super cool effect in the realm of what CSS should be able to do. |
This was resolved by the CSS Working Group at the New York City F2F in 2022 and has now been edited in and published in the css-values-5 FPWD. Please file any follow-up considerations as new issues. :) Closing this one out. |
Hi,
I see many authors (myself included) rely on JavaScript's
Math.random()
to generate a number they can then use to randomize colors, positions, animation delays etc. While it's fairly straightforward to do so (notably with custom properties), a potentialrandom
function (or keyword) in CSS would make a lot of sense, especially in conjunction withcalc()
and friends.AFAICT @tabatkins proposed something similar quite a few years ago, but I don't think we've discussed it recently, and I think we should reevaluate this option now that we have more context and background on how custom properties and mathematical expressions are being used in CSS.
The text was updated successfully, but these errors were encountered: