w3c / csswg-drafts

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

[css-borders-4] Allow to define separate border images for different border regions #9183

Open SebastianZ opened 1 year ago

SebastianZ commented 1 year ago

The slicing logic for border images is a relatively complicated concept that can confuse authors.

To make it easier to define border images, it should be possible to define multiple images instead of a single one that gets sliced.

The idea is to define up to eight individual images via the border-image-source property. Doing so, the border-image-slice property doesn't have any effect. The other border image properties would still work the same. When eight images are specified, each image targets one region of the border image area, starting at the top left corner and going clockwise through the regions. As indicated in the previous sentences, you may also specify less images. This allows to cover common use cases and may save network bandwidth when external images are used.

For filling the different regions I can imagine two different approaches.

Copy and auto-flip and/or auto-rotate the images

If less than eight images are defined, the images are used for the other regions as well.

E.g. if only two images are specified, the first one will be used for the four corner regions. It will be flipped horizontally for the corners on the horizontal opposite side and vertically for the vertical opposite side. The second image is then used for the four edges, rotated by 90° for each adjacent side.

Example:

border-image-source: url('corner.svg') url('edge.svg');

If three images are specified, the first one is used for the corners again, the second one for the top and bottom edges, and the third one for the left and the right edge, with the ones for the bottom and right edges rotated by 180°.

Example:

border-image-source: url('corner.svg') url('top-bottom-edge.svg') url('left-right-edge.svg');

Other combinations might be possible.

There may be use cases in which an auto-flipping and -rotating algorithm might not be wanted. For those we might introduce a new property that controls how to place the images.

Allow to target specific regions

Authors have to define the images for all eight regions but may skip them. That allows to place the images in specific regions within the border image area.

If authors want to add border images only in the bottom regions, they'd skip the top, left and right regions.

Example:

border-image-source:
  none none none /* top regions */
  none /* right edge region */
  url('bottom-right-corner.svg') url('bottom-edge.svg') url('bottom-left-corner.svg')
  none /* left edge region */;

If only the four corners should be decorated, authors would have to skip the edges.

Example:

border-image-source:
  url('top-left-corner.svg')
  none
  url('top-right-corner.svg')
  none
  url('bottom-right-corner.svg')
  none
  url('bottom-left-corner.svg')
  none;

Flipping and rotation like in the first proposed solution would still be possible via image manipulation functionalities. This would reduce the need for different images in some cases.

Sebastian

jsnkuhn commented 1 year ago

It was my personal experience that border-image-slice was the hardest property to get use to when I first started working with border-image. Have observed many others struggling with it as well. Even when I show people the MDN border-image generator they just tinker with it constantly clearly not understanding what is going on. Also cannot count the number of times people have "corrected" me when I tell them that the pixel value cannot have a px unit. They tell me: "That's just not how CSS works".

  1. Under "Allow to target specific regions" you mention "all eight regions." This seems to ignore the possibility of including a fill image. Is that intentional?

  2. Maybe a grid-template like syntax could be used when specifying all images?

border-image-source:
"url(top-left-corner.svg)    url(just_a_solid_line.svg)                         url(top-right-corner.svg)"
".                           .                                                  ."
"url(bottom-left-corner.svg) linear-gradinet(to top, #000 0 3px, transparent 0) url(bottom-right-corner.svg)";
  1. How would alignment of images work if for example the top corner images and top edge image where not the same height? Outer, center/middle, or inner aligned?

  2. I can see a lot of just_a_solid_line.svg/solid gradient being used to mimic solid bordered edges as part of the source. Could it be made possible to use something like border solid 3px as an image?? Or as a keyword in place of an image??

SebastianZ commented 1 year ago

Also cannot count the number of times people have "corrected" me when I tell them that the pixel value cannot have a px unit.

Agree that that's another pain point but let's keep that discussion in #6739.

  1. Under "Allow to target specific regions" you mention "all eight regions." This seems to ignore the possibility of including a fill image. Is that intentional?

Yes, because I see the fill image as another source of confusion. Border images are meant to be placed within the border area. For consistency reasons, a ninth image could be added, though I am strongly against that. For filling the background area we have the background-* properties.

The same accounts for border-image-width, which should not have existed, in my opinion. Or it shouldn't be possible for it to extend the actual border area, at least.

So my approach was to restrict it to the eight border regions.

  1. Maybe a grid-template like syntax could be used when specifying all images?
border-image-source:
"url(top-left-corner.svg)    url(just_a_solid_line.svg)                         url(top-right-corner.svg)"
".                           .                                                  ."
"url(bottom-left-corner.svg) linear-gradinet(to top, #000 0 3px, transparent 0) url(bottom-right-corner.svg)";

You mean grid-template-areas. The issue with that is that the string syntax conflicts with the <image> data type. You'd rather need to keep the <string>s for the dots separated from the <image>s. I.e. the example might look like this:

border-image-source:
  url(top-left-corner.svg)    url(just_a_solid_line.svg)                         url(top-right-corner.svg)
  ".                           .                                                  ."
  url(bottom-left-corner.svg) linear-gradient(to top, #000 0 3px, transparent 0) url(bottom-right-corner.svg);

But yeah, that could be another approach.

Another approach could be to target the different regions by name. Taking your example, that might then look like this:

border-image-source:
  url('top-left-corner.svg') top left
  url('just_a_solid_line.svg') top center
  url('top-right-corner.svg') top right
  url('bottom-left-corner.svg') bottom left
  linear-gradient(to top, #000 0 3px, transparent 0) bottom center
  url('bottom-right-corner.svg') bottom right;

Or maybe something that aligns with the grid syntax proposed for anchor positioning, allowing to fill multiple border regions with one image. Though I guess we might then run into similar issues again as we have them now with slicing.

  1. How would alignment of images work if for example the top corner images and top edge image where not the same height? Outer, center/middle, or inner aligned?

They are layed out using border-image-width, i.e. they are shrunk or stretched so that they are equal in size. As I said, all other border-image-* properties besides border-image-slice would still have an effect on the images.

We might still extend the features to allow aligning the images within their regions, but I explicitly only wanted to target the slicing issue for now and don't introduce new features besides that.

  1. I can see a lot of just_a_solid_line.svg/solid gradient being used to mimic solid bordered edges as part of the source. Could it be made possible to use something like border solid 3px as an image?? Or as a keyword in place of an image??

Authors could achieve that by placing a linear-gradient(transparent calc(50% - 1px), black calc(50% - 1px), black calc(50% + 2px), transparent calc(50% + 2px)). Pretty hacky, I know, but again, something better would be for a future extension. (And authors do have to fake that right now as well by creating an image tile like that.)

We might allow the stripes() function we just introduced for border-color for that, though. Which would reduce this to the much nicer stripes(transparent, black 2px, transparent).

Sebastian

jsnkuhn commented 1 year ago

Potential live site use cases for this change to border-image-source. It is of note that there are many more examples of border-image like styles on this page than I've called out below but I don't see border-image being used at all: https://baldursgate3.game/

  1. Around the entire element we have mirrored corners and a top edge mirrored to the bottom edge, and I guess right and left mirrored as well even though it's just a solid line.
border-image-source: url('corner.svg') url('top-bottom-edge.svg') url('left-right-edge.svg');
  1. Child call to action button is similar except without a right or left edge. So maybe?:
border-image-source: url('corner.svg') url('top-bottom-edge.svg') none;

image

jsnkuhn commented 1 year ago

I finally remembered where I had seen border-image properties like the ones proposed in this issue before and it was in the Emmet cheat sheet: https://docs.emmet.io/cheat-sheet/

Where these properties part of a previous proposal for border-image?

What would have been the function of continue keyword?

image

SebastianZ commented 1 year ago

I don't think these were part of any spec. At least I couldn't find it in the history of CSS Backgrounds 3 publications.

Though I found something along those lines proposed by Stuart Ballard[proposed] and elaborated by @fantasai and as comments on a request for corner images.

Introducing individual properties for each corner and side would be another way to achieve this. Though then we'd need to specify how they interact with the existing border-image-* properties.

Sebastian

jsnkuhn commented 1 year ago

How would missing border-image-sources be handled? Would one missing or slow to load image request result in the entire border-image not being painted? Or would the images that are available load in without the ones that aren't?

SebastianZ commented 1 year ago

How would missing border-image-sources be handled? Would one missing or slow to load image request result in the entire border-image not being painted? Or would the images that are available load in without the ones that aren't?

The same logic as for background-image applies, i.e. a missing image only affects the region(s) that image is used. The images that are loaded will still be displayed.

Sebastian

LeaVerou commented 9 months ago

I think border-image is fundamentally broken and instead of trying to fix it we should deprecate it and replace it with something else. Use cases for using images as borders are very, very common, and currently border-image not only doesn't cover enough of them, it has terrible DX for those it does cover.

I just posted https://github.com/w3c/csswg-drafts/issues/9714 to get the conversation started.

SebastianZ commented 9 months ago

I think border-image is fundamentally broken and instead of trying to fix it we should deprecate it and replace it with something else.

As I noted in the first comment and in the other issue, I believe the only big issue with border images is the slicing logic. Therefore, I believe it's better to extend and fix border-image rather than replacing it. Regarding the slicing problem, I just filed #9734.

Sebastian