w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.46k stars 657 forks source link

[css-images-4] Allow stripes to be used as gradients #7244

Open SebastianZ opened 2 years ago

SebastianZ commented 2 years ago

In https://github.com/w3c/csswg-drafts/issues/2532 it was discussed to allow one-dimensional images in two-dimensional contexts. Lately it was resolved to discuss this feature separately from the original use case of one-dimensional images.

So far, there were four ideas to achieve that. The direct way of adding <1d-image> to <image> is discussed in #7241. The other three all have in common that they refer to gradients or gradient-like funcions. I've summarized them here (in the order they were mentioned) including their pros and cons:

  1. Add <1d-image> to existing gradient functions

    Examples:

    background-image: linear-gradient(45deg, stripes(red, yellow, lime, blue));
    shape-outside: radial-gradient(stripes(black, transparent 20%));
    background-image: linear-gradient(45deg, black, stripes(red, yellow, lime, blue) 100px, white);

    Pros:

    • Existing gradient functionality is reused
    • Syntax is reused in different contexts
    • Besides linear stripes also radial and conical stripes and their repeating variants possible
    • Allows mixing stripes and gradients

    Cons:

    • Nested functions
    • Semantically incorrect
  2. Create separate stripes functions

    background-image: linear-stripes(45deg, red, yellow, lime, blue);
    shape-outside: radial-stripes(black, transparent 20%);

    Pros:

    • Similar to gradients without nesting

    Cons:

    • Behavior of thickness different to length in gradients
    • No reuse of <1d-image>
  3. Add keyword to existing gradient functions

    background-image: linear-gradient(45deg stripes, red, yellow, lime, blue);
    shape-outside: radial-gradient(stripes, black, transparent 20%);

    Pros:

    • Existing gradient functionality is reused
    • No nesting

    Cons:

    • Behavior of thickness different to length in gradients
    • No reuse of <1d-image>

Sebastian

SebastianZ commented 2 years ago

I have to say, even when it was me that brought up the keyword solution, it is my least favorite. I am split between option 1 and 2.

Option 1 allows reusing known gradient functions syntax and combines it with <1d-image> values. So, as @LeaVerou expressed it, we get the gradient controls for free. And any addition to the gradient functions or <1d-image> automatically works everywhere. For me, the big downside of this solution is that it reuses gradient terminology, which seems somewhat incorrect to me.

Option 2 is guided by gradient syntax but adds its own functions. This makes them semantically correct. And it still allows authors to define them in a similar way as gradients. But the big downside here is that the syntax regarding the color stops is not the same because the meaning of thickness is different from the positioning syntax for color stops in gradients. And another downside is that existing functions and data types are not reused. So, any additions to them also require some updates to those functions.

Sebastian

tabatkins commented 2 years ago

Agree that keyword is bad; divergent syntaxes based on keywords aren't great for authors, and make Typed OM cry.

I also think that, if we believe it's worthwhile to specify gradients with stripe syntax (which, I believe, hasn't been sufficiently established yet) that it's better to give them their own function rather than nest functions. (aka option 2 over all the others)

But the big downside here is that the syntax regarding the color stops is not the same because the meaning of thickness is different from the positioning syntax for color stops in gradients.

This is fine? It's literally the entire point of them, after all, and I'm not seeing why this is a problem in linear-stripes() but okay in linear-gradient(..., stripes()).

And another downside is that existing functions and data types are not reused. So, any additions to them also require some updates to those functions.

No, we'd just create a non-terminal for the first arg of each of the gradient functions, and then use it in both gradients and stripes. Then any updates apply to all at the same time.

SebastianZ commented 2 years ago

But the big downside here is that the syntax regarding the color stops is not the same because the meaning of thickness is different from the positioning syntax for color stops in gradients.

This is fine? It's literally the entire point of them, after all, and I'm not seeing why this is a problem in linear-stripes() but okay in linear-gradient(..., stripes()).

It might just be my gut feeling but linear-gradient(..., stripes()) looks more explicit than linear-stripes(). But yeah, it's a weak point.

And another downside is that existing functions and data types are not reused. So, any additions to them also require some updates to those functions.

No, we'd just create a non-terminal for the first arg of each of the gradient functions, and then use it in both gradients and stripes. Then any updates apply to all at the same time.

What I meant by that is that neither the gradient functions nor the new <1d-image> data type are reused by that option. Though you're right that we could share more between both types of functions to mitigate this issue. We might also introduce a <flex-color-stop> / <stripe> data type for the stripes syntax, which could be shared with the stripes() function and reused in other places in the future.

I currently also slightly tend to option 2 (with the adjustments mentioned above), as its semantic seems a little clearer to me.

Sebastian

faceless2 commented 2 years ago
background-image: linear-stripes(45deg, red 25%, yellow 30%, lime 40%, blue);

But the big downside here is that the syntax regarding the color stops is not the same because the meaning of thickness is different from the positioning syntax for color stops in gradients.

The syntax isn't fully described in this issue - the lengths above would be the thicknesses? So we have a red stripe from 0-25%, a yellow stripe from 25%-55%, a lime stripe from 55%-95% and blue for the final 5%?

This is a sensible way way to specify them, but I think it also means it must be option 2 as we really shouldn't make the meaning of lengths within a function depend on some other parameter.

Overall I really like this suggestion - it's essentially syntactic sugar for a linear-gradient with a 0-length transition between colors, which are a bit of a pain to specify when there are lots of stripes.

SebastianZ commented 2 years ago
background-image: linear-stripes(45deg, red 25%, yellow 30%, lime 40%, blue);

But the big downside here is that the syntax regarding the color stops is not the same because the meaning of thickness is different from the positioning syntax for color stops in gradients.

The syntax isn't fully described in this issue - the lengths above would be the thicknesses? So we have a red stripe from 0-25%, a yellow stripe from 25%-55%, a lime stripe from 55%-95% and blue for the final 5%?

Correct. The actual syntax for the color stops hasn't landed in the spec. yet but it is defined in #7029. With that syntax, the blue stripe would actually be interpreted as flexible value taking up the remaining space. So, in this case it is equivalent to 5%. In the following example red and blue would split the remaining space of 30% between them in a ratio 2:1:

background-image: linear-stripes(45deg, red 2fr, yellow 30%, lime 40%, blue 1fr);

Same here with the remaining space being 100% - 50px:

background-image: linear-stripes(45deg, red 2fr, yellow 20px, lime 30px, blue 1fr);

This is a sensible way way to specify them, but I think it also means it must be option 2 as we really shouldn't make the meaning of lengths within a function depend on some other parameter.

The meaning of lengths also stays the same in option 1 because of reusing stripes() which uses this syntax within the gradients functions.

I can see the logical conflict here as authors might expect that the same syntax is shared between both but this issue also applies to option 2.

Overall I really like this suggestion - it's essentially syntactic sugar for a linear-gradient with a 0-length transition between colors, which are a bit of a pain to specify when there are lots of stripes.

Right, I mentioned that earlier somewhere, but yes, the point of this is to simplify 0-length transitions between colors. That means e.g. linear-stripes(red, yellow, lime, blue) would be equivalent to linear-gradient(red 25%, yellow 25%, yellow 50%, lime 50%, lime 75%, blue 75%) and instead of linear-gradient(red 20px, yellow 20px, yellow 50%, lime 50%, lime calc(100% - 20px), blue calc(100% - 20px)) you could write linear-stripes(red 20px, yellow, lime, blue 20px). In addition to that we get flexible values, which isn't really possible at the moment (or at least extremely cumbersome because you'd have to calculate the percentages yourself).

Sebastian

SebastianZ commented 1 year ago

I've added a note and an example to my initial comment that the syntax with nested functions would allow for mixing gradients and stripes.

Given that, I still slightly tend to option 2. And others seem to agree with that. So let's see if we can resolve on that.

Sebastian

LeaVerou commented 1 year ago

Weak preference for Option 1. I think it's easier for authors to combine two concepts that they know than to learn about a third concept, even if the third concept is very very similar to the two existing concepts. Option 1 is something some may even try anyway, whereas Option 2 is something you either know about or don't.

Agreed on the arguments against Option 3, yeah, let's not do that.

fantasai commented 1 year ago

I agree with @tabatkins in https://github.com/w3c/csswg-drafts/issues/7244#issuecomment-1115391083 :

I also think that, if we believe it's worthwhile to specify gradients with stripe syntax (which, I believe, hasn't been sufficiently established yet) that it's better to give them their own function rather than nest functions. (aka option 2 over all the others)

Wrt

No reuse of <1d-image>

I don't think we care? How we organize our non-terminals is not something authors should ever need to care about.

LeaVerou commented 1 year ago

No reuse of <1d-image>

I don't think we care? How we organize our non-terminals is not something authors should ever need to care about.

But it does affect the language: Re-using <1d-image> means that if we define more types of 1D images down the line, they work with gradients out of the box, without anyone having to resolve, spec, and implement new syntax.

tabatkins commented 1 year ago

That's not at all guaranteed. "It works in the spec" doesn't mean we get the implementation for free - see all the bugs where browsers still don't support calc() in various spots that take a numeric value.

Skipping a little spec work isn't a great argument to organize something a particular way, especially if that implies a suboptimal authoring syntax (nested functions). I'm also not sure how likely a second 1d-image even is.

LeaVerou commented 1 year ago

That's not at all guaranteed. "It works in the spec" doesn't mean we get the implementation for free - see all the bugs where browsers still don't support calc() in various spots that take a numeric value.

Skipping a little spec work isn't a great argument to organize something a particular way, especially if that implies a suboptimal authoring syntax (nested functions). I'm also not sure how likely a second 1d-image even is.

Not sure if this is in reply to me, but I'm not talking about spec work at all, I'm talking about the author facing syntax.

SebastianZ commented 1 year ago

I believe, the main advantage of option 1 besides reusing existing syntax is that it allows mixing smooth gradients and stripes. This is not possible with the other options.

Given the example from the original post:

background-image: linear-gradient(45deg, black, stripes(red, yellow, lime, blue) 100px, white);

This resembles a gradient using current syntax of:

background-image: linear-gradient(
  45deg,
  black,
  red calc(50% - 50px), red calc(50% - 25px),
  yellow calc(50% - 25px), yellow 50%,
  lime 50%, lime calc(50% + 25px),
  blue calc(50% + 25px), blue calc(50% + 50px),
  white);

So, it gets rid of all the complicated calculations required now. And for anyone wondering how that looks like, here's the result:

Linear gradient made up of smooth gradients and stripes

Also, option 1 and 2 are non-exclusive. So we may go with 2 first to cover the stripes-only use cases and discuss the mixed-gradient-and-stripes case separately.

Sebastian

LeaVerou commented 1 year ago

I believe, the main advantage of option 1 besides reusing existing syntax is that it allows mixing smooth gradients and stripes. This is not possible with the other options.

That is an excellent point. Composability FTW. I think I'm now strongly in favor of Option 1.

  1. Add <1d-image> to existing gradient functions [...] Cons [...]
    • Semantically incorrect

What if we call it bands() instead of stripes()? Then it doesn't seem that incorrect inside gradients…

Loirooriol commented 1 year ago

A color stop has a color and 0-2 length-percentages.

Here it seems that you are using 100px as the size of the stripes, and let them be auto placed between the adjacent color stops. But what if the author want to control the position of the stripes?

I tend to think it's more consistent to just keep using 0-2 length-percentages for the position, instead of the size.

The behavior could be that stripes() produces 2 color stops (start and end positions of the stripes), and the area inside is painted with the stripes. Then,

linear-gradient(90deg, stripes(magenta, yellow, lime));
linear-gradient(90deg, stripes(magenta, yellow, lime) 0% 100%);
linear-gradient(90deg, magenta 33.33%, yellow 33.33% 66.66%, lime 66.66%);

linear-gradient(90deg, black, stripes(magenta, yellow, lime), black);
linear-gradient(90deg, black, stripes(magenta, yellow, lime) 33.33% 66.66%, black);
linear-gradient(90deg, black, magenta 33.33% 44.44%, yellow 44.44% 55.55%, lime 55.55% 66.66%, black);

linear-gradient(90deg, black, stripes(magenta, yellow, lime) 50%, black);
linear-gradient(90deg, black, stripes(magenta, lime) 50%, black);
linear-gradient(90deg, black, stripes(magenta, lime) 50% 50%, black);
linear-gradient(90deg, black, magenta 50%, lime 50%, black);

tabatkins commented 1 year ago

background-image: linear-gradient(45deg, black, stripes(red, yellow, lime, blue) 100px, white);

I actually skipped over this example, sorry. Now I'm not at all sure what it's actually supposed to be doing. That middle argument appears to be expecting the 100px to be a width, rather than a position (which is what all other lengths in a gradient are). How is that meant to mix with the other stops?

I'd assumed that the option 1 was about allowing <1d-image> as an alternative to the <color-stop-list> production. That is straightforward to define (tho it's still nesting functions). I'm just not sure what exactly you're expecting with your more complex example. I think at best you'd have to write that as:

linear-gradient(
  45deg,
  black,
  stripes(red, yellow, lime, blue) calc(50% - 50px) calc(50% + 50px),
  white
);

That is, the 1d-image would have to be paired with a double-position, and it defines the color within that range (using the range's size as the background painting area, basically).

The important question here, tho, is: how much do we expect this sort of thing to actually occur, such that it's worthwhile to add this additional complexity? Complex gradients can be a little verbose to define already; is that stopping people from using gradients in those cases? Do we have reason to believe that this type of complex gradient is something authors want to use a lot, so they'd benefit from making it easier?

css-meeting-bot commented 1 year ago

The CSS Working Group just discussed [css-images-4] Allow stripes to be used as gradients, and agreed to the following:

The full IRC log of that discussion <emeyer> SebastianZ: We had several discussion of how to bring stripes() syntax into gradients
<emeyer> …There were three proposals initially, but the third was ruled out
<emeyer> …So we now have two choices
<TabAtkins> q+
<emeyer> …Add the stripes() function to existing gradient functions
<lea> q+
<emeyer> …Or add new functions just for stripes
<emeyer> …Both have pros and cons
<emeyer> …Tab suggested option2, Lea said option 1, I think we should go with option 2 for now but keep discussing option 1
<astearns> ack TabAtkins
<emeyer> TabAtkins: My big question is whether we have a reasonable argument for making this easier to do
<emeyer> …You can already do stripes in gradients, with a different syntax and with some more author work
<emeyer> …The question is whether stripe images like this are common and complex enough that writing them in gradients as they are now is an impediment to authors
<bramus> q+
<fantasai> +1 to everything TabAtkins said
<astearns> ack lea
<emeyer> …I just don’t feel like it’s been established that there’s a big need for this in the authoring community
<emeyer> lea: The whole point of defining stripes as a 1D image was that we intended to use them in other places in CSS
<TabAtkins> q+
<fserb> q+
<emeyer> …Defining how 1D images can be used in place of gradient colors stops can be much more useful down the line
<emeyer> …Defining ad-hoc gradient functions sets us up for having to define a lot of specific functions
<emeyer> …Combining two concepts they already know is somethuing they might do accidentally
<emeyer> …We could say that the stripes() function has to be in place of the color stops at first, and relax that later
<emeyer> …I want to be able to talk about how 1D images should work
<emeyer> …If we want to do something like this, there could be value in renaming stripes() to something more broad
<emeyer> …Even in the border use case that started this, it’s weird to set a border to stripes()
<astearns> ack bramus
<emeyer> …maybe bands would be a better term
<bradk> 'radial-gradient(stripes,' doesn’t seem better than 'radial-stripes('
<emeyer> bramus: I always have to look up how to do this in existing gradients, and there are a lot of tools out there to do this properly
<emeyer> …I’m leaning toward option 1, which seems nice to me
<astearns> ack TabAtkins
<emeyer> TabAtkins: The point of defining of defining 1D images was to have a concept for 1D images which are only useful for borders
<emeyer> …The ability to re-use this for certain 2D cases was discussed but was not the reason we did it
<argyle> q+
<astearns> ack fserb
<emeyer> fserb: First, I agree with when Bramus said about people trying to do stripes on gradients
<emeyer> …Independent of what Tab said, I think option 1 looks better
<emeyer> …What does the 100px in that option mean?
<emeyer> SebastianZ: In my proposal it was the width of the stripes
<lea> q+ also do we define conic-stripes too? repeating-*-stripes? Do we add new stripes functions for every new gradient function? It's a combinatorial explosion…
<lea> q?
<astearns> example: background-image: linear-gradient(45deg, black, stripes(red, yellow, lime, blue) 100px, white);
<lea> q+
<emeyer> …So that would be a 100px-wide areas for the stripes, so each stripe would be 25px wide
<emeyer> …This is different to the color-stop syntax we currently have
<emeyer> …My point was to make it easier to express a width for all the stripes
<TabAtkins> I am indeed finding a bunch of "gradient stripe generators" from a quick search, so I wouldn't object to linear-stripes()/etc
<emeyer> …We could use color-stop syntax as Oriol pointed out
<emeyer> fserb: The syntax currently defines the image as proportional to whatever?
<emeyer> SebastianZ: The current use cases are just for borders and outlines
<emeyer> …There, it takes the border width into account
<TabAtkins> lea, yes, we would do the full set.
<fantasai> +1
<emeyer> …So in this case, I wanted to say “let’s define a width for this”
<emeyer> fserb: This seems like a generic problem of stripes
<fantasai> s/+1/+1 to TabAtkins/
<emeyer> …Maybe the linear gradient syntax is more consistent
<astearns> ack argyle
<argyle> https://shorturl.at/or689
<lea> thought experiment: how would we do the opposite? How would we produce a <1d-image> that is a gradient? I'm not saying let's do it, but it's not entirely unthinkable, especially once <1d-image>s start being used all around (e.g. strokes, paths etc) We'd need to be consistent.
<emeyer> argyle: I’ve been making alots and lots of stripes and it’s hard to find online the syntax easiest to manage
<emeyer> …In the link I shared, I provided multiple stripe examples
<lea> argyle: I created the whole technique of using CSS gradients for patterns back in 2010, so you are not alone here! :)
<emeyer> …Would ths proposal fix some of the hard stop problems, allowing aliasing
<emeyer> …Sometimes stripes aren’t even; sometimes the syntax is used to do easing
<TabAtkins> q+
<SebastianZ> q+
<emeyer> …If we’re going for stripe convenience, I’d like to see aliasing as an option
<astearns> ack fantasai
<emeyer> fantasai: Want to support everything Tab said
<emeyer> …If we’re going to add this, we should use option 2
<bradk> +1 to #2
<emeyer> …If we want to do a composable thing, then I think we should add linear-pattern and radial-pattern that are dedicated to establishing those patterns
<emeyer> …Trying to shoehorn stripes() into gradient functions is awkward
<smfr> +1
<emeyer> …I don’t think it’s a good plan
<astearns> ack lea
<TabAtkins> (note, for example, that a repeating stripes, which would be great, isn't currently compatible with putting stripes() directly into repeating-linear-gradient() - the way that flex is defined is incompatible with the way that the repeating width is found
<TabAtkins> )
<fantasai> s/establishing those patterns/composing 1D patters, including 1D stripes() and gradient()/
<emeyer> lea: Want to point out the proposal says we’ll define linear and radial stripes, but there’s more than that: conics, repeating gradients, maybe mesh gradients later on
<astearns> ack TabAtkins
<argyle> conic stripes https://shorturl.at/itzDR
<emeyer> TabAtkins: To Adam, stripes() is not just about evenly-sized stripes
<emeyer> …So long as they are solid-color stripes of some size, you’re good to go
<emeyer> …The transition between color areas is not defined
<SebastianZ> q-
<emeyer> …I presume stripes would work similarly to linear gradients
<emeyer> …That should be fine to allow, but it would be good to raise as an issue that we should say so in the spec
<fserb> I wonder if we are going to end up defining a 1d-image gradient, to do linear-gradient(45deg, gradient(red, white)...)
<emeyer> …The idea of just using stripes() directly in gradient functions
<emeyer> …In some ways, it’s just incompatible, as with repeating gradients
<emeyer> …You could not use a repeating-linear-gradient with a flex-defined stripe
<emeyer> …The concepts are just different enough that they need special handling
<emeyer> …That’s why I don’t think this is do-able in a generic way
<fantasai> fserb, that's why I don't think we should shove this into the gradient functions. If we want composing the functions, we should add a composition function that's better suited to composing.
<lea> q+ to reply to TabAtkins doesn't this depend on how the <1d-image> is used in the gradient? E.g. it could be used with two color stops, and treated as basically a black box (well, line)
<fserb> fantasia, yeah, it makes sense.
<fserb> *fantasai
<emeyer> …Google shows there are a lot of stripe generators, so the need does seem to exist
<fserb> argh
<argyle> +1 to dedicated functions
<bramus> +1 to that
<emeyer> fserb: You mentioned a bunch of functions, but that’s different than what Elika said, right?
<emeyer> fantasai: I was suggesting both; I think it’s convenient to have dedicated functions in parallel with gradient functions
<SebastianZ> +1
<lea> a composition function is more complex and confusing than either option, and we're just hoping it will save us complexity down the line…
<florian> +1
<emeyer> …If we want composable things, we need to define those
<astearns> ack lea
<Zakim> lea, you wanted to reply to TabAtkins doesn't this depend on how the <1d-image> is used in the gradient? E.g. it could be used with two color stops, and treated as basically a
<SebastianZ> q+
<Zakim> ... black box (well, line)
<emeyer> lea: To Tab, who said stripes wouldn’t work with repeating gradient syntax, wouldn’t that depend on the syntax we choose??
<emeyer> TabAtkins: Yes, that would work, but that would be the most complex and least justified of the syntax options
<fantasai> +1
<astearns> ack SebastianZ
<emeyer> SebastianZ: I think fantasai’s suggestion is a good path forward
<TabAtkins> the *-stripes() function family, paralleling gradients
<fserb> I like composition functions more than stripe specific functions, but I agree that adding stripe support to gradient is not great.
<emeyer> …Let’s introduce new stripe-gradient functions and later on, pursue a way to mix both stripes and gradients
<emeyer> …At some point we could have in the image-1D data type some gradient function as well that could be used inside other patterns
<TabAtkins> (these are basically just sugar for a gradient)
<emeyer> …You could use both, having both gradients and stripes
<emeyer> …So, option 2 for now
<TabAtkins> Yeah, I'd prefer waiting on for a third example before we try to generalize.
<fserb> that's fair too
<emeyer> astearns: I think that’s about all we could resolve on today, absent any objection
<bradk_> Straw poll?
<emeyer> astearns: I’m not sure there’s sufficient enthusiasm to start that work
<TabAtkins> +1 to adding *-stripe() functions
<emeyer> …Are there any objections to adding the stripe function family, as in option 2?
<bramus> Would be great for authors already
<emeyer> (silence)
<emeyer> RESOLVED: We go with option 2 and worry about composability in the future