-
Notifications
You must be signed in to change notification settings - Fork 711
[css-shapes-2][css-borders-4] Add a way to change an element's shape #6997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
this issue about trying to get borders etc to play nice with clip-path seems related: #5881 |
I think we should either extend I was thinking it could be a keyword we add to those properties, as in |
Also, add |
Big +1 to Brad's comments. We have |
with the reuse of a
does fell a bit strange hiding the corner-shape-ing just inside wonder if something like |
Any solution found here needs to define how to deal with If we end up with extending Sebastian |
Btw. just referencing #2180 in regard of the Sebastian |
I don't really think giving There are plenty of existing use cases of having a rectangular shape that is revealed with a |
Ran across this page with lots of interestingly shaped elements: https://atlus.com/persona5/home.html |
I think the solution here is a property that allows the author to specify a shape that affects the painted border, like |
I think this is a duplicate of #5881 in some ways. We should perhaps discuss them as one thing? |
Here's a very rough proposal for a
If the If the single If the double
Box shadows follow the outer and inner border shapes as appropriate.
The clipping of content behaves as it does for The shape variants of [1] Maybe the rectangular border also renders, and the author has to set the color to Here's a screenshot for some in-progress experiments in WebKit:
|
I'm sure other people would have more to say about the specifics, but my initial reaction is "let's do it!" |
On second thoughts, since the stroke-based and area-based shapes take their colors from border-color, the normal rectangular borders should not be rendered. |
@smfr what does the two-shape syntax add on top of using a single .shapre {
padding: 40px;
overflow: hidden;
border: 60px solid transparent;
box-shadow: 0 0 20px black, 0 0 20px black inset;
border-shape:
shape(from 0% 0% evenodd,
hline to 100%,
vline to 75%,
smooth by -8.295% 0% using -3.7% -2.2%,
smooth by -8.5% 12.8% using -4.1% 10.7%,
smooth by -7.9% -3.2% using -3.7% -5.2%,
smooth by -7.3% 9.3% using -2.7% 7.1%,
smooth by -9.0% 0% using -4.1% -2.3%,
smooth to 0.82% 100% using 0.82% 100%,
close,
move to 10% 10%,
hline to 90%,
vline to 64.621%,
smooth by -8.295% 0% using -3.7% -2.2%,
smooth by -8.5% 12.8% using -4.1% 10.7%,
smooth by -7.9% -3.2% using -3.7% -5.2%,
smooth by -7.3% 9.3% using -2.7% 7.1%,
smooth by -9.0% 0% using -4.1% -2.3%,
smooth to 10% 90% using 10% 90%,
close) border-box;
background-image: linear-gradient(to bottom right, orange, blue, green), none;
background-origin: border-box;
background-clip: border-area, border-box;
}
``` |
The single-shape syntax is a request to stroke the given path (which may contain multiple sub-paths). The two-stroke syntax is a request to fill the area between the paths. It's possible we should switch between these behaviors with a keyword, but then you might have an author providing a single-path path and requesting a fill, which would be an error (I don't think we ever want the border to act as a fill over the entire element). |
Maybe I'm still getting this wrong, but is this equivalent to using a different winding-rule for fill and stroke? |
I don't think so. Authors might want to specify different winding rules for the two-path form. It's about wanting the path to be stroked, vs. wanting the path to be filled. But maybe we should also have the two-path form allow stroking as well? |
@smfr For the example above, I'm curious, how would adding |
Just for confirmation we are changing the entire shape of the element here correct? If so shouldn't the property name be "element-shape" in seat of "border-shape"? The border is only that shape because it traces/strokes the element's shape. It also seems that a "border"/stroke would not be required and therefore calling it "border-shape" could be confusing. A border-less "border-shape" just seems odd to me. If you have a border-less |
It's consistent with |
The current proposal doesn't have a way to just get that border on the left edge (with rounded ends), though. |
A little more detail on our motivation: It's reasonable to view border-shape as providing three levels of increasing complexity & power for specifying borders: the border-* properties define a rounded rect path that gets stroked; the single-path version defines an arbitrary path that gets stroked; the two-path version defines an Iarbitrary shape that gets filled. Generally, we want increasing complexity to track with increased power, so authors that want only a small deviation from the built-in stuff can just go up a small amount in complexity, rather than having to jump all the way to some "primitive" version that's super hard to work with (but super powerful). So, the "rounded-rect" borders can have up to four distinct widths and colors, one for each horizontal/vertical side, with the rounded corners smoothly interpolating between the values. Our proposal is, we think, both the simplest and most natural way to reproduce this ability in the single-path syntax, with the path direction dictating which of the four border-width/color values to draw from (or which two to interpolate between, and by how much). If you use the single-path to produce a rounded rect, or any reasonably close variation on that, you'll get a result essentially identical to what is defined for the border-* properties today. (The way the corners are interpolated will be slightly different, which I think is unavoidable - I don't think the corner interpolation rules can be applied to an arbitrary path without an explosion of complexity. But I could be wrong about that.) If you do other shapes, you'll still get a reasonable result, with decent control knobs to twiddle. If this still isn't enough, the two-path syntax still exists, but we think this should provide enough power to the single-path syntax that you'll often be satisfied. We could also provide a further intermediate syntax, where you can provide the colors and widths along the path explicitly, tied to the path distance; similar to defining gradient stops. We're already using that syntax in gradients and in linear(), and will likely use it in more places (cubic splines for more easing?), so it is a familiar pattern to work with. This is more complex than needed for simple shapes, but is still vastly less complicated than the two-path syntax. |
Here's my fuller proposal, in more detail:
|
A couple of questions/clarifications:
|
I understand the motivation for the single path syntax taking its widths from
I don't think this will work very well if you apply it to boxes with different aspect ratios; the author would have a hard time matching gradient points with the box corners.
I'm not sure it does? I think we can fill the space between the inner and outer shapes with border colors by default, with the usual diagonal corner joins. |
I guess that's what I understood from #6997 (comment)? |
That was with |
Is this a limitation of CoreGraphics or a more general issue? To my knowledge, Skia is okay with variable-width strokes (@fserb ?). (I think it's okay if we end up doing something simpler by default, fwiw, so long as it's possible to do variable widths/colors in the 1-path syntax somehow in a reasonably usable manner. Like, worst case, we could just take the border-top width and color, or something like that.)
Yeah, this is definitely a special-case "one specific box" sort of thing, which doesn't make me very happy. We need something to control this, tho; even if you fall back to the two-path syntax, you'd then need to do a one-off fill image, which is just as wonky. The only other suggestion I would have is being able to match up the widths/colors with the path commands (or rather, between the commands), either with a separate list that has a 1:1 correspondence, or inserting them directly into the shape() syntax. I lean toward the latter; we can just say that contexts that aren't stroking the path would ignore that information (or if they're stroking it but doing a stroke fill some other way, they'd ignore the color info). That way, you could reproduce a rounded-rect with 8 path commands, as normal, and just place the desired border-width/color before/after each straight-line segment; the corner arcs would interpolate. Or, of course, do something else, like in this example (where the angled corners take the color of the left/right sides, rather than being half-colored by each side). We already might want to put in corner-rounding controls into shape() between commands (rather than always requiring them to be communicated separately, and applying equally to all corners), so I think this fits in quite naturally.
What's determining the angle/position of the color change there? It can't just be radial lines from the center, as that would mean the three sharp corners would only look right if the border-width ratios happened to match the box's own aspect ratio. |
Or, another alternative: define another function, Then, individual contexts would define whether they expect a So the one-path syntax would actually be a |
Exactly the same as the color joins with border-radius and different border widths; I think the joins are the connecting lines between the corners of the border box and the padding box. |
I'm still not sure how that works - the four corners can all have completely different angles, and lines extended from them aren't guaranteed to meet in a single point (in fact, they usually won't). Do you, like, find their first intersection points, then connect those points with a line, to divvy up the interior into four zones (some of which might be trapezoidal), and then fill the border shape accordingly? |
That's what seems the most simple and logical to me to do with varying |
Some thought about how This is an issue with I wonder if |
The CSS Working Group just discussed
The full IRC log of that discussion<emilio> noamr: I can give a bit of a recap<emilio> ... we resolved at TPAC on having a draft for border-shape <emilio> ... I started working on that and discovered some questions <emilio> ... one of them is how it relates to corner-shape <emilio> ... other one is how it works with border-width etc <emilio> ... so wanted smfr to demo what he has <emilio> ... and then talk demos <emilio> smfr: [shows prototype] <emilio> ... uses border-shape with the recent `shape()` function <emilio> ... so it effectively takes over the rendering of the rounded rect <emilio> ... and replace it with effectively two shapes <emilio> ... one for the inside of the rect, one for the outside <emilio> ... actually first outside, second inside <emilio> ... it uses two different beziers, and it shows background-clip: border-area, and also box-shadow which both follow those shapes <emilio> ... the way I think about this is replacing the rendering of the radius <emilio> s/radius/border/ <emilio> ... it follows the same rules regarding clipping of the content etc, just like border-radius <emilio> ... that's the two-shape version of this <emilio> ... [switches demo] <emilio> ... this one is the one-shape version, with a hole <noamr> q+ <emilio> ... I think I used border-top-width for the line width <emilio> ... but single-shape has issues re. that interaction <emilio> ... a separate draft of mine just specifies effectively a stroke width <emilio> ... interpolating seems hard <emilio> ... also this is purely decorative. Affects clipping and ink overflow, but layout still uses the general box model <emilio> q+ <emilio> ... what that means is that you might specify shape that allow the inside edge to project outside of the border-box <emilio> ... which exposes things like extra background area or what not which are a bit weird <TabAtkins> q+ <astearns> ack noamr <emilio> ... one other thing I wanted to mention the connection between this and corner-shape is that this breaks assumptions <emilio> noamr: one of the oddities is how it interacts with border-width <emilio> ... with border-radius it kinda works <emilio> ... so that's one oddity of this <emilio> ... if your shape is kinda boxy you probably want your border-width to affect it <emilio> ... I think border-width would be a default you can override <emilio> ... same with border-color <emilio> ... my thinking was that this could behave like a mask going through the borders <noamr> https://github.com//issues/6997#issuecomment-2389134315 <emilio> ... would be a simple solution but not what people would want <fserb> q+ <emilio> ... I think this should be separate from corner-shape <emilio> ... we should restrict corner-shape to rectangles <emilio> ... where we can use it with border-radius <emilio> ... and this would smooth your radius rather than something that controls shape <emilio> ... would be good to separate it at least in the beginning <emilio> ... and we'd leave the interaction problems for later <bramus> emilio: i was wwondering if this corrected border matches stuff how border-image works today <bramus> … not sure how border-image and border-radius interact <bramus> … feels similar <bramus> … in the sense that b-i disregards b-w and layout <bramus> … would be good to be consistent witht hat <florian> q+ <astearns> ack emilio <bramus> … can find some answers to the weird questions here <astearns> ack TabAtkins <emilio> TabAtkins: generally agree with emilio, border-image does use border-width for the area but you can override <emilio> ... we should approach this to keep this simple, the geometry should be overridden by shape, which is what simon is suggesting as well <emilio> ... supportive of not paying attention to color or width as much as possible <emilio> ... had some suggestions but not fully fledges <emilio> ... having some way of specifying variable colors along the path seems nicer than trying to interpolate based on existing border properties <schenney> +1 to Tab's color/width idea. <schenney> Interpolate between points. <emilio> smfr: sounds hard, not sure if svg has a way of changing colors along the path <emilio> ... nice suggestion <emilio> TabAtkins: what about variable width stroke? <emilio> smfr: you need to use multiple paths for that <emilio> TabAtkins: that's kind of annoying <emilio> ... quite a jump from border-radius to some two-path <schenney> q+ <emilio> weinig: PostScript model is a bit old, updating it might be reasonable <RRSAgent> I have made the request to generate https://www.w3.org/2025/01/29-css-minutes.html fantasai <emilio> ... sticking just to pdfs had in late 90s got us pretty hard <emilio> ... pushing that seems reasonable <astearns> s/hard/far/ <emilio> ... we can build the paths and draw new stuff <emilio> ... would just be slow <emilio> ... doesn't mean that we need to be constrained, we should be able to push that <emilio> ... we know how to do the conversion from border-radius to multiple paths <emilio> TabAtkins: I agree that as long as it's not unusably slow we should be able to do this, authors would just have to wait for animation use cases <emilio> fserb: two things <astearns> ack fserb <emilio> ... first, I think I tend to agree with Tab that it might be possible to given one shape and automatically build the two things <emilio> ... it is probably possible <emilio> ... need to be careful about where it begins <emilio> ... I don't think we need more infrastructure from low level graphics APIs for this <emilio> ... if we have a good way of determining how the inner shape works from the outer and a width <emilio> smfr: I think some shapes might be pretty hard, like shapes with loops and what not <emilio> fserb: I think that works less for colors than for width <emilio> ... I don't know what colors mean on a shap <emilio> ... I think leaverou wanted to do quadrants, but even that seems a bit sketchy <TabAtkins> (i'm fine with abandoning gradient stroke) <emilio> ... but would be nice to try this for border <emilio> ... easy way is to lose some of those other properties, but maybe we regret this <noamr> q+ <emilio> ... Maybe it's worth trying to connect border-width to this, even if it's probably hard <astearns> ack florian <fantasai> +1 fserb <emilio> florian: back to an earlier point, the fact that this would be disconnected from layout border-width <emilio> ... it might be seen as a feature <emilio> ... controlling the layout so taht it doesn't interact with the border seems useful <emilio> ... so defaulting stroke to top <TabAtkins> yeah, taking the default width/color from border-top seems fine <emilio> ... and using left/right/bottom for layout adjustments seems nice <astearns> ack schenney <emilio> ... maybe weird but convenient <fantasai> or average all the colors? :) <emilio> schenney: so in your original demo, where would scrollbars be if you had overflow: scroll? <TabAtkins> scrollbar-path: shape(...) <emilio> noamr: The problem with scrollbars is the same for border-radius, it appears on top <emilio> ... ugly, but needed for a11y <florian> scrollbar-path ? <emilio> ... this conversation about the border stuff being this default makes sense <emilio> schenney: so we're leaning towards making this not affect the layout right? <emilio> correct <emilio> smfr: Firefox applies extra constraints to border radius for scrollbars <emilio> ... maybe the spec could allow that for border-shape too <astearns> ack noamr <emilio> noamr: wanted to go back to separate it from corner-shape <emilio> ... so keep corner-shape for boxes, and border-shape for other stuff <noamr> q+ <florian> q+ <emilio> smfr: so do we want to merge corner-shape and border-shape or keeping separate? <TabAtkins> would prefer separate <astearns> ack noamr <fserb> +1 separate <TabAtkins> q+ <emilio> noamr: one argument against combining is that corner-shape interacts with border-radius <astearns> ack florian <emilio> smfr: you could also see a border-shape that's mostly rounded as an enhancement on border-radius <emilio> florian: so border-shape: auto uses the rest, and otherwise you override <ydaniv> q+ <emilio> emilio: but you have two props at that point already <emilio> q+ <bramus> emilio: dont think that is exclusive <bramus> … if you keep the on the same property you can still define sth like that <bramus> … if corner shape is superellipse then you account for border radius and take over <bramus> florian: could be worng that corner shape thing has not 1 value but 1, 2,4 … <bramus> … it has longhands to go along with this <bramus> … whcih border-shape does not <bramus> … its 4 props (or more) rolled into 1 <bramus> … where border-shape is 1 thing <bramus> emilio: that’[s a good argument <astearns> ack TabAtkins <emilio> TabAtkins: pretty strong to keep them separate <emilio> ... as it was brought up, generic paths have a lot of complexity <emilio> ... that prevents us from making assumptions <emilio> ... well defined corner shapes don't have those constraints, and we can use border-width etc <emilio> ... so I think corner-shape should be on the general thing, and the path version should do its own thing on top <astearns> ack ydaniv <emilio> ydaniv: we could consider reversing the names, conceptually <emilio> ... having a shape as the shorthand and border / corner as longhands <emilio> ... maybe think how they interact <emilio> ... may make it more complicated tho <astearns> ack emilio <emilio> astearns: so leaning about separate properties? <emilio> smfr: yeah, modulo bikeshedding name <emilio> noamr: yeah I think last bikeshed was border-shape, but we can always change it in the future <emilio> border-shape: <shape> <shape>?; + strokes TBD (top border width default + override) <astearns> ack fantasai <emilio> fantasai: I agree we should have border-{width,color} in <emilio> ... I think the border shorthand... ? <emilio> ... what's the plan for corner-shape? <emilio> fantasai: for color we might want the border-top-left color, or block-start, or averaging all the four colors <emilio> ... all slightly awkward <TabAtkins> block-start, definitely. i don't think averaging will produce anything meaningful. ^_^ <emilio> astearns: maybe separate issue? <fantasai> s|colors|colors/widths| <emilio> noamr: details I need is (1) corner-shape is different <emilio> PROPOSED: corner-shape and border-shape are independent properties <fantasai> none sgtm <emilio> PROPOSED: border-shape overrides corner-shape/border-radius and co <fserb> sgtm <emilio> RESOLVED: corner-shape and border-shape are independent properties <emilio> ack fantasai <astearns> ack fantasai <emilio> fantasai: I think necessarily one has to override the other, and border-shape needs to override the other <emilio> ... we could create a shorthand for both border and corner shape <emilio> ... I don't see how we could have both applying at the same time <noamr> that shorthand would have to also have border-radius <fantasai> yes <bramus> emilio: consensus is that you cant have both appplied at th esame time and you cannot make them be the same property if you want corner-shape to be a shorthand <bramus> florian: yes, and we could have a super shorthand but can discuss separately <bramus> fantasai: yes, what i am saying <bramus> emilio: should also discuss things like them being reset or not by the shorthand <bramus> astearns: a super shorthand sound scary :) <emilio> RESOLVED: border-shape overrides corner-shape / border-radius <bramus> emilio: should we resolve on it not affecting layout too? <bramus> astearns: yes <bramus> PROPOSED RESOLUTION: border-shape is a non-layout affecting property <bramus> RESOLVED: border-shape is a non-layout affecting property <emilio> PROPOSED: border-shape doesn't affect layout, only ink overflow and graphic effects like clipping and shadows <bramus> astearns: other things to discuss? <bramus> noamr: the derfault stroke and color are taken from the existing border props. can say tbd which property exactly <emilio> PRPOSED: default stroke width / color get taken from existing border props <emilio> which ones TBD <emilio> smfr: maybe TBD is ok but I think it's fine to do the diagonal color joint in the issue <emilio> RESOLVED: default stroke width / color get taken from existing border props, specifics TBD |
One proposal for Break it down in two steps. Step 1. Assume a Step 2. How to build Would something like this work? |
I'm going to open separate issues for this once the draft is in. Let's add these ideas there |
Closes w3c#6997 This defines the 'border-shape' property with a narrow set of details that we've resolved on, leaving room for discussing some details as we go along. Specifically, this adds the two variants of 'border-shape' (single/double <basic-shape>). Issues to be opened separately: * interaction with border-{width|color|style} * Clipping replaced elements * Lots of examples
@LeaVerou mentioned an
element-shape
property in #6980 (comment) as a way to influence the shape of an element. The idea behind that property is to change the shape of the element so that border, shadows, etc. follow that shape.So far we have the
shape-outside
property which influences the float area. I suggest to extend this property to be able to specify that it not only affects the float area but also all the other boxes created for the element.Sebastian
The text was updated successfully, but these errors were encountered: