w3c / csswg-drafts

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

[css-background-4] Replace `corner-shape` with property that just does angled corners + rounding #8591

Open LeaVerou opened 1 year ago

LeaVerou commented 1 year ago

At this point, it's very clear that there is little implementor interest for corner-shape as it's currently specified. It has been in the spec for over a decade, so this is unlikely to change.

However, looking at the corner-shape use cases in #6980, I am observing these things:

a) The vast majority just need bevel (angled) corners. scoop and notch are rather rare and niche enough that I think are fine being delegated to CSS shapes or border-image. b) Quite frequently, these angled corners also require rounding, which the current syntax does not actually do.

Therefore, the current design of corner-shape which treats border-radius as a fallback with the same radius, won't work (and there has been feedback that this is a worse fallback than nothing at all for most use cases).

Angled corners

Instead, I propose a corner-angle property (name TBB) which only does angled corners. It takes the same syntax as border-radius, with keywords for common cases, such as:

Brainstorming for property name: - `corner-angle` - `corner-cutout` - `cutout-size` - `bevel-radius` - `corner-size` - `angled-corners` - `cutout-corner-size`

Angle rounding

It seems that rounding is essential to satisfy the use cases in the real world, otherwise authors will still resort to images. border-radius could work together with the new property to add rounding to the angles generated. This has the advantage of re-using an existing property, but it has some pretty major disadvantages:

First, border-radius is designed to specify rounding for 1-4 corners, whereas this syntax can create up to 8 angles:

image

So, how would we map border-radius's four corners to these eight? We could map it to pairs (e.g. border-radius for top left corner rounds both 1 and 2 above), but that's pretty weird. But the primary problem is pointless complexity and wasted implementation effort to make any possible border-radius value work with these polygons, when the vast majority of use cases just needs a single radius. Personally, I have not seen single a single use case that requires more than that.

So perhaps we need a separate property (angle-radius?) that just takes a single <length>. Though it is a bit weird that this basically becomes a subset of border-radius when corner-angle is initial.

Other considerations

Whatever syntax we go with, we should make sure it allows combining regular border-radius with this, e.g. it should allow having two angled corners (with or without rounding) plus two rounded corners (with the full power of border-radius).

cc @fantasai @SebastianZ

kizu commented 1 year ago

I really like this proposal!

Also, I think I need to mention one case which could be one of the most common, and which also falls in line with a lot of the recent work on popovers and stuff (html modals/popovers, anchor positioning) — the popovers' tip/arrows. So it would be really awesome to also have this in our toolbox.

Here is what I mean when trying the https://projects.verou.me/corner-shape/

image

Basically, most current implementations of the tips/arrows are either using the border hack, or using a rotated item, or masks, while using something like a corner-angle seems like a very obvious usage for this.

AmeliaBR commented 1 year ago

This seems like a lot of added complexity for a fairly niche style.

Clip-path with polygon() already supports basic bevels so long as you don't mind actually clipping & don't need a border style.

Then, the missing feature would be to create a border that follows the clipped shape, or to create that shape without actually clipping child overflow. So, my suggestion would be to create a border-shape property that takes a <basic-shape> value, the same as clip-path, but it only clips the backgrounds and then strokes the shape in the border width & style.

(Complication: which border-width & style? The shape wouldn't necessarily have 4 sides, but the browser would still need to handle a mix of border styles! Maybe: divide up the border box into triangles created by diagonal lines and stroke the sections of the border-shape that fall in each triangle according to that border style.)

For rounding corners after bevelling, I'd be happy to see a single corner-radius parameter added to the polygon function, for all of the places where the function is used: clip-path, shape outside, offset-path, maybe one day SVG. Or maybe two corner-radius parameters, one for convex corners and one for concave.

(Long term, I'd also like to get an accepted syntax for combining percentages, unit-lengths, and calc/math function with SVG path notation, so you could get all sorts of shapes! But polygons with slightly rounded corners is a nice, simple solution to many use cases.)

Benefits of replacing corner-shape with a property that uses <basic-shape> values:

Downside:

LeaVerou commented 1 year ago

@AmeliaBR

This seems like a lot of added complexity for a fairly niche style.

I disagree that it is niche. Perhaps actual beveled corners are niche, but the kinds of polygons one can create with this are all over the web. https://github.com/w3c/csswg-drafts/issues/6980 has several use cases.

So, my suggestion would be ...

It looks like this is basically my earlier element-shape proposal. The problem with all of these is that it is very difficult to define all the border edge cases, border-radius has been a huge pain for this reason and nobody wants to do this work for arbitrary shapes. This is exactly why there is no implementor interest for corner-shape, and your proposal makes the problem even harder, not easier. Furthermore, this doesn't really cover the extremely common case of a polygon with rounded angles, unless you are willing to spell the path out with (pretty lengthy, and inflexible) SVG, at which point you may as well have been using an image. And, as you point out, even for simple shapes, the syntax can get complicated fast.

AmeliaBR commented 1 year ago

it is very difficult to define all the border edge cases, border-radius has been a huge pain for this reason

Only because the spec encourages elegant transitions in style and width as the border goes around the corner! If we accept that fancy borders with different width/colour/styles on each side are edge cases & it's OK to have hard stop changes, then my suggestion of clipping the stroke shape to the box diagonals is easy enough to implement.

SebastianZ commented 1 year ago

So, my suggestion would be ...

It looks like this is basically my earlier element-shape proposal.

For which I created a separate issue because it goes way beyond corner shapes.

If we accept that fancy borders with different width/colour/styles on each side are edge cases & it's OK to have hard stop changes, then my suggestion of clipping the stroke shape to the box diagonals is easy enough to implement.

@AmeliaBR mask-border already goes into that direction, though it just clips the border, it doesn't have any influence on the stroke shape.

@LeaVerou Are the transitions between borders the reason implementers are reluctant to implement corner shapes? Or is it the missing specification regarding those transitions in Level 4? Or is it something else?

I assume the implementation of bevel shapes in combination with different widths, colors and styles is much easier than for rounded corners. And those are implemented more or less interoperably for so many years now. But it would really be good to get some implementer feedback on the reasons.


Regarding the two suggested approaches, I think Lea's idea is easier for authors to use in general.

Though I wouldn't actually throw away the idea of the general corner-shape property. Instead of introducing a new property that only covers bevels, we could add a function for that to corner-shape. The big advantage is that we'd only have one new property which can be extended to cover other use cases in the future. And it still plays well with border-radius.

Picking up the examples from above, this could look like:

corner-shape: bevel(50%); /* diamond */
corner-shape: edge edge bevel(100%); /* triangle top left */
corner-shape: bevel(50%) bevel(50%) 0 0 / bevel(100%);

Though those examples are rather edge cases, anyway. More common cases would be something like corner-shape: bevel(10px).

So, how would we map border-radius's four corners to these eight? We could map it to pairs (e.g. border-radius for top left corner rounds both 1 and 2 above), but that's pretty weird. But the primary problem is pointless complexity and wasted implementation effort to make any possible border-radius value work with these polygons, when the vast majority of use cases just needs a single radius. Personally, I have not seen single a single use case that requires more than that.

I'd rather stay with border-radius for that and I don't think it is that weird to let it apply to two actual corners on the shape.

The implementation complexity can be reduced by restricting the values of border-radius that apply when corner-shape is something else than edge (the default). E.g. it could be specified that only values without vertical radii (i.e. without the values after the slash) are applied.

Sebastian

LeaVerou commented 1 year ago

@LeaVerou Are the transitions between borders the reason implementers are reluctant to implement corner shapes? Or is it the missing specification regarding those transitions in Level 4? Or is it something else?

You would need to ask implementers :)

And it still plays well with border-radius.

Yeah, I'm not convinced that's an advantage :)

E.g. it could be specified that only values without vertical radii (i.e. without the values after the slash) are applied.

Note that you can get elliptical radii with a single border-radius value, if it's a percentage.

SYwaves commented 1 year ago

So in general, I agree with adding an additional property to work with border-radius, and it taking some form of angle hinting, but I'm going to throw out a crazy idea here:

pig2

Using cubic bezier function to draw the radius, with corner-angle taking angle values for the "handles", and border-radius redefined as the scalars.

Default value would be 0deg, and straight can be used to sit the angle on a straight line. This should cover for both scoop and bevel; notch can't be, but that should be covered with other tricks, and is niche, as you say.

Downside is that you can't perfectly represent a circle with cubic-bezier.

LeaVerou commented 1 year ago

@SYwaves using cubic-bezier() for the corners has been discussed many, many times. Nobody doubts it would be useful, but it introduces even more complexity than the current corner-shape property, and we are trying to reduce complexity so that it has chances of getting implemented, not increase it. It would be good to come up with a syntax that allows for such extensions in the future but it's not an option right now. We don't want something that will sit in the spec unimplemented for another 10 years.

SebastianZ commented 1 year ago

And it still plays well with border-radius.

Yeah, I'm not convinced that's an advantage :)

E.g. it could be specified that only values without vertical radii (i.e. without the values after the slash) are applied.

Note that you can get elliptical radii with a single border-radius value, if it's a percentage.

Right, elliptical radii probably aren't expected in combination with bevels or other corner shapes.

So giving this a little more thought, I now tend to agree with you that it might be better to introduce a separate property for that. It's also better in regard of implementations because it could be handled separately.

@LeaVerou Are the transitions between borders the reason implementers are reluctant to implement corner shapes? Or is it the missing specification regarding those transitions in Level 4? Or is it something else?

You would need to ask implementers :)

@tabatkins, @smfr, @emilio What needs to happen to get some attention for this feature?

Sebastian

emilio commented 1 year ago

I'm not the expert on the graphics-side of border-radius, but ftr I don't think it's a trivial feature at all (it infests a lot of the background / shadow / border / clipping / hit-testing / etc code). In that sense, a clip-path-like thing seems a lot easier to implement (you don't care about semi-transparent borders, stuff overlapping etc etc).

I think at least from the CSS / layout side the current corner-shape: {round,angle} thing seems fine. In an ideal world, thinking more as an author, something that supersedes border-radius seems a bit better. Maybe we could make border*-radius shorthands for corner-*-shape: radii(<length-percentage>) ... or so? But maybe not worth the complexity and the current thing seems like it'd be less effort to implement (compared to adding a new per-corner property plus all the relevant shorthands).

Anyways this doesn't look too hard to implement, specially if it's built on top of border-radius as it is on the current backgrounds-4 spec. But it doesn't seem to be in the "trivial-ish" camp either. I'd have to defer to @lsalzman / @mstange / @jrmuizel / @glennw / @kdashg for actual graphics-knowledge. My guess is that this is probably not technically hard to do, just some / a bunch of work to make fast.

SebastianZ commented 1 year ago

@emilio Thank you for the detailed feedback! Just to be clear, as I understand you, you'd prefer the currently specified corner-shape over the corner-angle proposal made in this issue (disregarding the angle-radius which is rather orthogonal)?

Sebastian

SYwaves commented 1 year ago

@LeaVerou I see. If that's the case, is it not easier to extend another property rather than working around current implementation of border-radius? Like adding a property to draw a stroke on clip-path, or expanding round to be used with polygon() instead of just inset rectangles.

I suppose that's just shifting the issue elsewhere, but I think the need to draw arbitrary shapes isn't exactly isolated to just the bounding box and border-radius.

jsnkuhn commented 1 year ago

Opened "standards positions" github issues for 'corner-shape' with Mozilla and Webkit just to try and get this on folks radar. Mozilla has a response, nothing yet from WebKit:

Mozilla: https://github.com/mozilla/standards-positions/issues/823 WebKit: https://github.com/WebKit/standards-positions/issues/229

smfr commented 2 months ago

I'm playing with corner-shape in WebKit now, mostly as a vehicle for supporting squircles somehow. But having implemented bevel, I do have some thoughts:

smfr commented 2 months ago

https://github.com/w3c/csswg-drafts/issues/6980#issuecomment-1023357465 has a mixed-corner-treatment example.

jsnkuhn commented 2 months ago
smfr commented 2 months ago

on the potential intersecting scoops: Isn't this true of any shape? Even round? Aren't there clamping rules for border-radius that deal with this?

No, the "indent" type corners (scoop, notch) need additional constraints to avoid overlaps.

tabatkins commented 2 months ago

In particular, the diagonally opposite corners might intersect. The existing radius-clamping rules do indeed prevent adjacent corners from intersecting.

See, for example:

.box {
  width: 100px; height: 100px;
  border-radius: 100px 0 100px 0;
  border: thin solid;
  corner-shape: scoop;
}

angle corner shape is the maximal cut you can do without running into issues; anything that cuts out more needs additional shrinking rules.

smfr commented 2 months ago

The lower shape here shows intersecting scoop corners (before implementing any additional constraints):

scoop