Skip to content

Commit

Permalink
[css-borders-4] Specify corner-shape based on superellipse (#11606)
Browse files Browse the repository at this point in the history
* [css-borders-4] Specify `corner-shape` based on superellipse

This specifies the `corner-shape` group, including:
- general description and interaction with border-radius
- all the individual corners, side shorthands, and overall shorthand
- multiple keywords, and how they translate to a `superellipse()`
- The superellipse formula, and how it is rendered
- How the exponent of the superellipse interpolates

Open issues (will open separately):
- Add a few examples
- Resolve on "straight" vs "none" for the convex angle.
- Resolve on the exact interpolation formula
- Define restrictions for border rendering

Closes #10993
Based on resolution #10993 (comment)

Co-authored-by: Tab Atkins Jr. <[email protected]>
  • Loading branch information
noamr and tabatkins authored Jan 30, 2025
1 parent 947cd87 commit 85e0ca0
Showing 1 changed file with 155 additions and 43 deletions.
198 changes: 155 additions & 43 deletions css-borders-4/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -336,29 +336,168 @@ The 'border-radius' shorthand</h4>
See [[CSS3BG]].

<h3 id=corner-shaping>
Corner Shaping: the 'corner-shape' property</h3>
Corner Shaping</h3>

By default, non-zero border-radii define a quarter-ellipse that rounds the affected corners.
However in some cases, other corner shapes are desired.
The 'corner-shape' property group specifies a reinterpretation of the radii to define other corner shapes.

The different shapes applicable to 'corner-shape' can be expressed as different parameters to a superellipse.
A superellipse is a closed curve resembling an ellipse, and based on its `k` parameter can express all the shapes between a square, an ellipse, and a notch.

To allow full expression as well as interpolation, the 'corner-shape' properties can provide its own superellipse parameter using the 'superellipse()' function,
or use one of the supplied keywords which represent commonly used parameters. See the <<corner-shape-value>> definition for details.

<h4 id=corner-shape-rendering>
Rendering 'corner-shape'</h4>
'corner-shape' works alongside 'border-radius', and does not have any visual effect with a 'border-radius' of 0.
It acts as an alteration on top of the default round 'border-radius', and thus can be used as progressive enhancement.

Like 'border-radius', 'corner-shape' clips elements according to the [=overflow=] rules, and applies to the rendering of the border.
Since stroking a superellipse accurately may be computationally intensive, user agents may approximate the path using bezier curves,
as well as account for sharp edges and overlaps.

Issue: 'border-radius' already handles *adjacent* corners overlapping by shrinking the radiuses proportionally.
A negative ''superellipse()'' parameter allows for *opposite* corners to sometimes overlap, and needs additional restrictions defined.

Issue <a href="https://github.com/w3c/csswg-drafts/issues/11610">#11610</a>: check if we need additional rendering restrictions.

<h4 id=corner-shape-value>
'corner-shape' values</h4>

<pre class=prod>
<dfn><<corner-shape-value>></dfn> = ''round'' | ''scoop'' | ''bevel'' | ''notch'' | ''straight'' | ''squircle'' | superellipse(<<number [0,&infin;]>>)
</pre>

<dl dfn-type="value" dfn-for="<corner-shape-value>">
<dt><dfn>round</dfn>
<dd>
Border radii define a convex elliptical curve at the corner. Equivalent to <css>superellipse(2)</css>.

Note: this is the initial value of 'corner-shape' properties, as elements with 'border-radius' would be rounded.

<dt><dfn>scoop</dfn>
<dd>Border radii define a concave elliptical curve at the corner. Equivalent to <css>superellipse(0.5)</css>.
<dt><dfn>bevel</dfn>
<dd>Border radii define a diagonal slice at the corner. Equivalent to <css>superellipse(1)</css>.
<dt><dfn>notch</dfn>
<dd>Border radii define a concave 90deg angle at the corner. Equivalent to <css>superellipse(0)</css>.
<dt><dfn>straight</dfn>
<dd>Border radii define a convex 90deg angle at the corner.
This would have the same visual effect as a 'border-radius' of 0. This is different from having a 'border-radius' of 0 when animating.
Equivalent to <css>superellipse(infinity)</css>.
<dt><dfn>squircle</dfn>
<dd>Border radii define a convex curve between an ellipse and an convex angle, equivalent to <css>superellipse(4)</css>.
</dl>

Issue <a href="https://github.com/w3c/csswg-drafts/issues/11607">#11607</a>: resolve on ''straight'' vs <css>none</css>.

The <dfn>superellipse( <<number>> | infinity )</dfn> function describes the curvature of the corner.
It accepts a <dfn>superellipse exponent</dfn>, which defines the curvature of the corner, or the exponent of the formula.
The [=superellipse exponent=] accepts values between 0 (a straight concave angle) and <css>infinity</css> (a straight convex angle),
with the values in between representing the curvatures in between.

The ''superellipse()'' formula can be described in cartesian coordinates, as follows,
where <code>a</code> is the horizontal ''border-radius''
<code>b</code> is the vertical ''border-radius'', and <code>k</code> is the [=superellipse exponent=]:

<pre>
|x/a|^k + |y/b|^k = 1
</pre>

<h4 id=corner-shape-shorthand>
Corner Shaping: the 'corner-shape' and 'corner-*-shape' properties</h4>

<pre class="propdef">
Name: corner-shape
Value: [ round | angle ]{1,4}
Value: <<corner-shape-value>>{1,2} / [ <<corner-shape-value>>{1,2} ]?
Initial: round
Applies to: all elements, except table element when 'border-collapse' is ''collapse''
Applies to: all elements where 'border-radius' can apply
Inherited: no
Animation type: discrete
Animation type: see individual properties
</pre>

By default, non-zero border-radii define
a quarter-ellipse that rounds the affected corners.
However in some cases, other corner shapes are desired.
The 'corner-shape' property specifies a reinterpretation of the radii
to define other corner shapes.

<dl dfn-type="value" dfn-for="corner-shape">
<dt><dfn>''round''</dfn>
<dd>Border radii define a convex elliptical curve at the corner.
<dt><dfn>''angle''</dfn>
<dd>Border radii define a diagonal slice at the corner.
</dl>
Applies the shape to all corners, following the same rules as ''border-radius''.

<pre class=propdef>
Name: corner-top-left-shape, corner-top-right-shape, corner-bottom-right-shape, corner-bottom-left-shape, corner-start-start-shape, corner-start-end-shape, corner-end-start-shape, corner-end-end-shape
Value: <<corner-shape-value>>
Initial: round
Applies to: all elements where 'border-radius' can apply
Inherited: no
Logical property group: corner-shape
Computed value: the corresponding ''superellipse()'' value
Animation Type: see [=superellipse interpolation=]
</pre>

The [=flow-relative=] properties
'corner-start-start-shape',
'corner-start-end-shape',
'corner-end-start-shape',
and 'corner-end-end-shape'
correspond to the [=physical=] properties
'corner-top-left-shape',
'corner-bottom-left-shape',
'corner-top-right-shape',
and 'corner-bottom-right-shape'.
The mapping depends on the element’s 'writing-mode', 'direction', and 'text-orientation',
with the first start/end giving the block axis side,
and the second the inline-axis side
(i.e. patterned as 'corner-<var>block</var>-<var>inline</var>-shape').

<pre class=propdef>
Name: corner-top-shape, corner-right-shape, corner-bottom-shape, corner-left-shape,
corner-block-start-shape, corner-block-end-shape, corner-inline-start-shape, corner-inline-end-shape
Value: <<corner-shape-value>> [ / <<corner-shape-value>> ]
Initial: round
Applies to: all elements where 'border-radius' can apply
Inherited: no
Computed value: see individual properties
Animation type: see individual properties
</pre>

<p>The 'corner-*-shape' shorthands set the two 'corner-*-*-shape'
longhand properties of the related side. If values are given before
and after the slash, then the values before the slash set the
horizontal radius and the values after the slash set the vertical radius.
If there is no slash, then the values set both radii equally.
The two values for the radii are given in the order
top-left, top-right for 'corner-top-shape',
top-right, bottom-right for 'corner-right-shape',
bottom-left, bottom-right for 'corner-bottom-shape',
top-left, bottom-left for 'corner-left-shape',
start-start, start-end for 'corner-block-start-shape',
end-start, end-end for 'corner-block-end-shape'
start-start, end-start for 'corner-inline-start-shape',
and start-end, end-end for 'corner-inline-end-shape'.
If the second value is omitted it is copied from the first.


<h4 id=corner-shape-interpolation>
Interpolating corner shapes</h4>

Since a <<corner-shape-value>> can always be expressed by a ''superellipse()'' with an [=superellipse exponent=] variable, interpolating between two
<<corner-shape-value>>s is done by interpolating the [=superellipse exponent=] itself.
Since it's an exponent, interpolating it linearly would result in an effect where concave corners interpolate at a much higher velocity than convex corners.
To balance that, the <dfn>superellipse interpolation</dfn> formula describes how a [=superellipse exponent=] is converted to a value between 0 and 1, and vice versa:

<div algorithm="superellipse-exponent-to-interpolation-value">
To interpolate a <<number [0,&infin;]>> |exponent| to an interpolation value between 0 and 1:
1. If |exponent| is 0, return 0.
1. If |exponent| is &infin;, return 1.
1. Return <code>1/(2^(1/|exponent|))</code>.

To convert a <<number [0,1]>> |interpolationValue| back to a [=superellipse exponent=]:
1. If |interpolationValue| is 0, return 0.
1. If |interpolationValue| is 1, return &infin;.
1. Return <code>ln(0.5)/ln(|interpolationValue|)</code>.
</div>

<a href="https://github.com/w3c/csswg-drafts/issues/11608">Issue #11608</a>: resolve on this or another interpolation formula.

<pre>

</pre>

<div class="example">
For example, the following declarations create a right-pointing next button.
Expand Down Expand Up @@ -398,33 +537,6 @@ Corner Shaping: the 'corner-shape' property</h3>
How to allow custom corners? Perhaps a ''path()'' function? Or a ''cubic-bezier()''?
Something else?

<h3 id="corners-shorthand">
Corner Shape and Size: the 'corners' shorthand</h3>

<pre class="propdef shorthand">
Name: corners
Value: <<'corner-shape'>> || <<'border-radius'>>
</pre>

The 'corners' shorthand sets 'corner-shape' and 'border-radius' in the same declaration.
If either is omitted, it is reset to its initial value.

<div class="example">
For example, the following declaration creates a diamond shape.
<pre>corners: angle 50%;</pre>
In UAs that don't support 'corner-shape', the declaration is ignored
(falls back to a rectangle).
</div>

<div class="example">
In this example, the first declaration creates tabs with vertical sides and rounded corners using 'border-radius',
while the second example makes them trapezoid-shaped in UAs that support 'corners'.
<pre>
border-radius: 0.25em 0.25em 0 0;
corners: angle 0.25em 0.25em 0 0 / 50% 50% 0 0;
</pre>
</div>

<h2 id="partial-borders">
Partial borders</h2>

Expand Down

0 comments on commit 85e0ca0

Please sign in to comment.