Open Afif13 opened 1 year ago
+60deg
is just 60deg
with an explicit sign, so that alone won't work.
But + 60deg
with a space between +
and a dimension token could be a way to express incremental values.
Edit : ++60deg
might be more readable. As CSS tokens this is a delim token +
and a dimension token with explicit sign.
+60deg
is just60deg
with an explicit sign. But+ 60deg
with a space between+
and a dimension token could be a way to express incremental values.Edit :
++60deg
might be more readable. As CSS tokens this is a delim token+
and a dimension token with explicit sign.
Yes, but I thought that since the +
is rarely used to indicate positive values we can do an exception inside gradients and use it as increment. Maybe we should consider another character to avoid conflict?
++
has the benefit of being well established in other languages and the intent is the same as it would be here : an implicit read and increment with whatever is on the right of ++
.
The only awkward thing about a syntax like this is that there really isn't a meaningful difference between ++60deg
, +-60deg
--60deg
, -+60deg
, +calc(30deg * 2)
.
++
and --
aren't real operators in this syntax. One symbol is a delim and the other is the sign of the dimension token.
If this is something you want to prototype as a PostCSS plugin, please let me know, happy to help out. I really like the work you do with CSS gradients!
I won't say no to a prototype 😊 if it's something easy to do why not. It can help showing real examples and make the proposal even better! Thank you :+1:
@Afif13 I really like this idea!
To make the syntax clear, are you thinking it would always either be a double-plus/minus, so either ++60px
or --60px
for example?
Another potential idea re the syntax that might be clearer—just thinking out loud here—is to introduce a function that only works in this context, like shift()
, which could be used like this:
.selector {
background: linear-gradient(red 20%, blue shift(20px), green shift(20px));
background: conic-gradient(red 60deg, blue shift(60deg), green shift(60deg), yellow shift(60deg));
background: conic-gradient(red shift(60deg), blue shift(60deg), green shift(60deg), yellow shift(60deg));
/* ┗━ when using shift without a previous value to shift from,
it would shift from 0, so `shift()` would essentially do
nothing as a first value, but still be valid syntax */
background: repeating-linear-gradient(red 0 10px, blue 0 shift(10px));
background: repeating-linear-gradient(red 0 shift(10px), blue 0 shift(10px));
}
Outside of these whitelisted contexts, shift()
would have no function. Obviously, this isn't as abbreviated as ++
and --
, but as I've been told with my own proposals, it's a lot easier to add a new function to accomplish something than a new syntax or operator, so I'm trying to be constructive here and find a solution that might work to introduce this.
In either case, whether doing ++
/--
, it would be neat, if not necessary, to make this work with nested functions/variables, like this:
.selector {
--default-shift: 10px;
/* ++ example */
background: linear-gradient(red 20%, blue ++var(--default-shift), green ++calc(var(--default-shift) * 2));
/* shift() example */
background: linear-gradient(red 20%, blue shift(var(--default-shift)), green shift(calc(var(--default-shift) * 2)));
}
cc @romainmenke
@brandonmcconnell the idea is not using ++
as the operator. As explained by @romainmenke, one plus will the positive sign while the other is the incremental operator so in your last example we would use
background: linear-gradient(red 20%, blue +var(--default-shift), green +calc(var(--default-shift) * 2));
Same logic with --
but I won't focus on this part because incrementing is the only useful operation inside gradient since we always need to have bigger values. Smaller values are not useful that's why my proposal is only about "incremental syntax".
It can also be another character like ~
to avoid confusion but in all the cases, the idea is to have an optional character to indicate if it's a static value or a relative one.
The shift()
function is good but .. a bit verbose 😅 I prefer a reduced syntax but if a function is the only way, I would take it.
You might be interested in taking inspiration from the relative color syntax, since the idea seems to express relative (to the previous color stop) vs. absolute color stop positions.
For example, linear-gradient(red, orange calc(prevStop + 50%), green)
.
@Afif13 Yeah, either syntax would be good in my opinion— +
or shift()
.
Also, I just did some testing, and it looks like either ++50%
or +(50%)
could be acceptable since neither are currently valid.
I thought +(50%)
might be valid syntax already, but it appears to break the style as far as I can see. I'm not suggesting we strictly stick to one of these but rather than either of these could be acceptable uses.
This is pretty similar to how we can use properties on numbers in JS, for example:
(5).toFixed(2)
5..toFixed(2)
Either of the above are valid, but 5.toFixed(2)
is not since a single dot without parentheses could be mistaken for a decimal delimiter.
Similarly in your proposal, a single sign could be valid outside parentheses, or two symbols adjacent without parentheses, which could be validly any combination:
++5px
+-5px
-+5px
--5px
And they each do different things, where the first sign dictates whether to add or remove the amount, and the second is the polarity of the actual number.
+(50%)
might be a bit less convenient because you can not drop the leading +
to toggle between absolute and relative. You also need to remove the parenthesis.
Have looked at this a bit more and there is one case that hasn't been called out in the examples explicitly.
When dealing with mixed units it is unknown which values are larger.
This won't work at all :
.foo {
background: linear-gradient(red 20px, blue 10vi, green 10vw, yellow 5rem, orange 10ex);
}
To express a progression with mixed units you currently need to wrap every preceding value with max()
. After a few stops this escalates dramatically.
This will work :
.foo {
background: repeating-linear-gradient(red 20px, blue 0 10vi, green 0 ++10vw, yellow 5rem, orange ++10ex);
}
And is equivalent to :
.foo {
background: repeating-linear-gradient(red 20px, blue 0 10vi, green 0 calc(max(20px, 10vi) + +10vw), yellow 5rem, orange calc(max(calc(max(20px, 10vi) + +10vw), 5rem) + +10ex));
}
This is not an argument in favor of any proposed syntax but it does show that this proposal tries to solve something that is really difficult today.
@romainmenke That looks great! Would it be possible to adjust the syntax so that +var()
or +calc()
assumes its content will be a length, dictated by the leading +
? That would make the feature much more flexible.
Also, re the max()
you made, I was going to mention it may prevent descending color stops (e.g. 100% ➞ 50% ➞ 0%), but upon testing, that may not be supported anywhere anyway.
Would it be possible to adjust the syntax so that +var() or +calc() assumes its content will be a length, dictated by the leading +? That would make the feature much more flexible.
That already works ;) I just didn't bother writing a test for it.
.calc-3 {
background: repeating-linear-gradient(red 10px blue +calc(5px * 3));
/* becomes */
background: repeating-linear-gradient(red 10px blue 25px);
}
.var-1 {
background: repeating-linear-gradient(red 10px blue +var(--foo));
/* becomes */
background: repeating-linear-gradient(red 10px blue calc(10px + var(--foo)));
}
@romainmenke Great! Yeah, I only made that comment re your "This won't work with var()
because we can not statically determine if var()
will resolve to a color or a length." note on https://github.com/csstools/postcss-plugins/pull/895, but I see you clarified "+var()
is fine, we know that that must be a length".
I really like this syntax per Temani's proposal and your impl. I'll play with it later. 👏🏼
For anyone interested, I ported @romainmenke's plugin to a CodePen instance for quicker tinkering, here:
https://codepen.io/brandonmcconnell/pen/YzOOBVj/66c65611ba8bf3133f1cf87ccfe09c44?editors=0100
Really enjoying this syntax, @Afif13. Great suggestion 💯
& TIL you can't mix %
and deg
units in conic-gradients, even using pure CSS 🤷🏻♂️
e.g. conic-gradient(red, blue +40%, blue 60%, green calc(60% + +10deg))
How does this work when color stop positions are omitted?
Does it first fill in the color stops as if the relative value was also missing? And then afterwards combine the interpolated position from the previous color stop with the relative value?
Or does it first apply the relative value as if the previous color stop had a value of zero?
linear-gradient(red, white ++10%, blue);
Is that :
linear-gradient(red 0%, white 10%, blue 100%);
linear-gradient(red, orange, white, pink ++10%, blue);
Is that :
linear-gradient(red 0%, orange 3.3333%, white 6.6666%, pink 10%, blue 100%);
linear-gradient(red 0%, orange 25%, white 50%, pink 60%, blue 100%);
linear-gradient(red, orange ++10%, white, pink, blue);
Is that :
linear-gradient(red 0%, orange 10%, white 40%, pink 70%, blue 100%);
linear-gradient(red 0%, orange 10%, white 50%, pink 75%, blue 100%);
@romainmenke
Actually, I never thought about this but now I can see a lot of complex cases to consider 😅
The issue I can see is an example like linear-gradient(red, blue ++50%, pink, green)
. If we first calculate all the color stops (including the pink one) we can easily find ourselves with the final value of ++50%
bigger that the value of pink
and we will need another Fixup step which is not good.
I think the suitable solution is to consider a relative color as static if there is no reference for it (no color stop is defined before it). The previous example will become linear-gradient(red, blue 50%, pink, green)
then we do the color Fixup to get linear-gradient(red 0%, blue 50%, pink 75%, green 100%)
So like you said "it first apply the relative value as if the previous color stop had a value of zero" sounds good to me.
pinging the Spec Editors so they can give us their feedback 🙏
@fantasai @LeaVerou @tabatkins
I think it's good to have a kind of "relative values" to express a relation between two consecutive color stops.
Let's say I have
linear-gradient(red 20%,blue 40%,green 60%)
where each time I am adding20%
to the color stops. It would be good if I can, instead, writelinear-gradient(red 20%,blue +20%,green +20%)
. The plus sign means "take the last color stop and increment it". This way we can easily express the20%
as a variable and reuse it.Here are a few more examples:
I think the
+
won't create any confusion in the syntax. It can be an optional character before the value to indicate if the value is static or relative.The calculation should also be done after the Color stop "Fixup" like I did in the last example. When doing
linear-gradient(red 150px, blue 70px +50px)
we should have150px + 50px
and not70px + 50px
.