-
Notifications
You must be signed in to change notification settings - Fork 682
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] if() function #3455
Comments
Very interesting idea! It got me thinking if there was a way we might be able to express a condition to test, and two results to pick between returning using CSS variables, and I think I have a basic demo of something that can work, but it's limited to only what JavaScript knows about (so right now I'm not sure how it would compare CSS units). demo: https://codepen.io/tomhodgins/pen/jXVMPW We can invent our own CSS variables like :root {
--if-1: '{"equal": [5, 5]}, "lime", "hotpink"';
--if-2: '{"equal": [5, 4]}, "lime", "hotpink"';
} Or set them on a tag in HTML like this (I wish the quotes didn't have to be encoded 😇): <html style='--if-computed: "{%22computed%22: [%22background-color%22, %22rgb(0, 255, 0)%22]}, %2220pt%22, %2210pt%22"'> Then we can write a function that supports the various conditions we want to test — whether it's mathematical comparisons between numbers, or checking the computed styles of properties on elements — anything that JavaScript can test for us can be written as a condition. In this demo I'm supporting function comparison(value, event, ruleOrTag) {
const [conditionObject, trueResult, falseResult] = JSON.parse(`[${decodeURI(value)}]`)
const [name, [left, right]] = Object.entries(conditionObject)[0]
const features = {
less: (left, right) => Number(left) < Number(right),
lessEqual: (left, right) => Number(left) <= Number(right),
equal: (left, right) => Number(left) === Number(right),
greaterEqual: (left, right) => Number(left) >= Number(right),
greater: (left, right) => Number(left) > Number(right),
computed: (left, right) => window.getComputedStyle(ruleOrTag)[left] === right
}
return features[name](left, right) ? trueResult : falseResult
} And then all we need to do to connect the dots between CSS and JS is this to loop through all of the rules in CSSOM and the tags in DOM, find all CSS variables that contain the information we want to pick between, and process what we find. For this I'm using a computed-variables plugin with a configuration like this: computedVariables('--if-', comparison, window, ['load']) This will find every CSS variable starting with How far do you think JavaScript + CSS variables could go toward implementing this sort of |
@tomhodgins ? 🤔 Are you just pitching your JS-in-CSS solution? I don't see how that's related to what I proposed. Sure you can simulate this in JS by parsing CSS vars. How is that relevant? 🤷♂️ |
I didn't invent CSS variables, I wrote a demo based on what you described that leveraged the built-in features of CSS. Everything I have done is something that can be done with CSS as it currently is specced and supported in browsers. I'm pitching using CSS.
Isn't this why CSS variables exist? If this gets close to what you're hoping to have someday in CSS, I wonder to what extent you can make use of CSS variables to simulate this sort of functionality in the meantime. |
You can do pretty much whatever you want with CSS variables, but because CSSOM is still in draft, any changes you make in actual output usually involves an entire replacement of a stylesheet's innerHTML, which will invalidate the style tree and can cause a document style recalculation / reflow. You can do it on a limited scale, but a JavaScript synchronous read / update process can never be as fast or as performant as a solution in CSS. The browser can immediately begin calculating a style tree very early in building the document. JavaScript-based solutions have to wait, look for styles, parse, calculate changes, and push updates, with more expensive side-effects. I know you've done a lot of impressive demos of what can be done with CSS variables, but it can never beat a native feature, especially because simulating "cascade effects" through JavaScript is difficult. It's why I would advocate for this even though it's in Less, because live variable bindings / cascade make it a more powerful possibility than a pre-processor (or JS live processor) can provide.
If you mean to be hook points for JavaScript polyfills, no. That is not their primary design purpose. That's mainly the goal of worklets and the CSS Paint API (Houdini). |
Definitely agreed, but short of actually implementing this in a browser how do can this sort of functionality be prototyped and explored? Building a demo to try it out using technology we already have seems like a good starting point. I'm not trying to say this shouldn't be a feature, the opposite. I'm trying to explore the idea and establish:
That's why I'm so confused by your reaction to this. I would have thought somebody wanting this feature like this would be excited about a demo, even if it's rough, because it proves something about the idea. If you think the idea is useful and that my demo isn't good enough to use in production, that's something good we have proven about it, and anybody can run the demo to see why it's not good enough for themselves and understand. Having the demo helps make the case for the feature. And having more demos should help make the case for it even better.
JavaScript being able to interact with CSS variables so easily seems like it was intentionally designed and supported even if it isn't their primary design purpose for existing. Right now CSS variables have a lot more browser support than the Paint API, so they seemed like a natural building block to begin exploring the concept and fleshing it out. If you wanted to experiment with totally custom syntax, CSS variables would still be a way you could include your custom syntax inside CSS in a valid way and handle parsing and processing of it outside of CSS while a feature like this was being worked on to help you avoid having to write invalid CSS. Even after a feature like this is added to CSS and begins to have browser support, at the time you want to start using it you'll probably need a frontend polyfill to support this feature in browsers that can't parse it, so the polyfill you would write in the future to support the feature in older browsers might not look so different than what a speculative prototype you build for this feature might look like today. I think it's worth thinking about. How would you prototype functionality like this to build a demo of it instead? I'd love to see a prototype of this idea working if you can implement it in a different way to test it out! |
Sorry I probably misunderstood and thought you were derailing. I should have asked for more clarification. My apologies. |
This is, unfortunately, what makes this so much harder, and probably not capable of happening. In your example, you seem to be assuming that Later, you talk about an alternate syntax that explicitly references other properties; this, unfortunately, means we're adding arbitrary dependencies between properties, something we've avoided doing so far because it's, in general, unresolvable. Custom properties can arbitrarily refer to each other, but they're limited in what they can do, and have a somewhat reasonable "just become invalid" behavior when we notice a cycle. Cycles are more difficult to determine for arbitrary CSS, and can happen much more easily, because there are a number of existing, implicit between-property dependencies. For example, anything that takes a length relies on
So, unfortunately, we've had to reject ideas like this in the past, and as far as I can tell, there's nothing mitigating the problems in this proposal either. Sorry. :( |
That's alright! And I would assume you would know the engineering challenges better than I would. I have to do the same rejections for Less when it's just not feasible with how statements/functions/vars are evaluated. I didn't have high expectations for Has there ever been a proposal / consideration of being able to define units, or capture values at a certain stage? That would maybe take CSS authors halfway. As in (this syntax is terrible, just illustrating the concept): .grandparent {
width: 200px;
height: 200px;
}
.parent {
@unit --foo 100ct; /* container height at this point, i.e. 200px */
width: 1px;
height: 1px;
position: absolute;
}
.grandparent .parent .child {
position: absolute;
width: calc(1--foo); /* use an inherited unit value, i.e. make .child same width as .grandparent */
} That is, if there was a way to query the cascade, or mark values (declared or inherited) in the cascade with vars to consume later, it would provide a lot of dynamism without logic switches. |
Incidentally:
You can adjust font-size to scale with width, as long as width is the viewport width. For instance, I have in a project where I do something like: html {
font-size: calc(0.625vw + 12px);
@media (max-width: 479px) {
font-size: 14px;
}
@media (min-width: 1260px) {
font-size: 20px;
}
} And then, for most of my UI, it looks like .title {
font-size: 1rem;
padding: 3rem;
} So font-size scales with width (within a range), and then UI scales with font-size. This isn't totally component-friendly, of course, since the missing needed values you need there are container width / height, not viewport. (Rarely is |
Yes, because viewport width can't be specified (in
There've been proposals, but inserting dependencies between elements in layout is just as fraught as dependencies between properties in the cascade. We very carefully design the layout algorithms to be solvable based on specific pieces of layout information being passed up and down the tree; if arbitrary layout information can move around the tree and influence other properties, we suddenly have a vastly more complicated situation to deal with. In the limit (which you hit pretty much immediately), we have to abandon the layout algorithms as written completely, and instead rephrase everything in terms of a constraint solver, letting the arbitrary new layout dependencies introduced by the author just fall out as they may. Arbitrary constraint solvers are, unfortunately, much slower in the general case than the layout algorithms are, so doing so would slow down all pages as well. Tangent: be careful with your units invention - |
lol whoops. Not a unit I've used. In any case, if I didn't say it already, I'm excited to see |
The addition of
min()
,max()
,clamp()
andtoggle()
to the CSS Values 3 draft has made a lot of great progress towards more expressive styles. One thing that would be a very powerful general purpose logic switch would be anif()
function. (Full disclosure, this matches the implementation in Less - http://lesscss.org/functions/#logical-functions-if, but would be much more powerful in the cascade.)It would have this signature:
if(condition, trueResult, falseResult)
The
condition
would match the semantics in Media Queries Level 4, and would query the computed properties of an element.For example, here's usage of an
if()
as an emulation ofmin()
Obviously, we already have
min
max
, but what's useful aboutif()
is that, liketoggle()
, you can use it for any value.You could also use it to toggle background based on inherited values.
Like
calc()
and like other languages that have a declarativeif()
function, you can of course nest it.Note that many other functions in CSS already have a kind of logic test built-in, but they're very restricted, such as
var()
itself, which tests for an inherited value, and has anelse
if the value is not defined.if()
would also pair nicely withtoggle()
, where the toggled value can be tested to resolve other property values.Anyway, that's for your consideration. Cheers!
The text was updated successfully, but these errors were encountered: