-
Notifications
You must be signed in to change notification settings - Fork 719
[css-shapes] Allow optional rounding parameter for polygon()
#9843
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
ping @astearns @atanassov (editors of CSS Shapes) |
The use case is valid, but I worry a little bit about diverging from SVG polygons (and the regular dictionary definition of “polygon”) for this. Could adding more unit flexibility in path() be an alternative solution? |
When looking at this through an SVG lens, wouldn’t it be much like an equivalent to |
Given the reluctance of browsers to work on SVG, and the fact that there is no SVG WG anymore to evolve SVG further, I think this in practice will end up holding the web platform back 😕 That said, adding more unit flexibility in |
I think this makes a lot of sense. Syntactically, I would flip the order of the |
What happens if the radius is big enough or the segments of the polygon small enough? Browsers already tend to break with These situations will be much easier to happen with arbitrary polygons. |
Just like with border-radius there’s a limit of how large you can make the radius and anything beyond that is scaled down, we can do the same here. Hopefully there’s a non-iterative way to calculate that, I can reach out to some geometry experts about the specifics once we have consensus to pursue the functionality. For an MVP, I wonder if we could come up with a much lower bound, since the vast majority of use cases only need a little rounding.
These have a lot to do with borders, which is not a consideration here. |
This sounds good! I presume that the circle radiuses would be clamped to the smaller of half the neighboring side widths, so two adjacent corners can't run into each other and you get well defined arcs+lines. Wrt differing from SVG polygon, the SVG polygon was intentionally made useless in the first place; it's literally just a polyline with a closing segment. I do not think we should take its (extremely harsh) limitations as any sort of limit on what we do with shapes. |
Agenda+ to resolve on consensus to work on this. |
Agreed, plus this would match better with what the rectangular functions already allow ( |
Hadn't realized there’s such precedent. Updated the proposal to reflect that. |
In the following example the neighboring sides are √101 (slightly longer than 10), so your condition would allow a radius of 5, but anything bigger than √1.01 is problematic, even without taking into account the rounding of the other corners. |
Sorry, you're right, it's not the radius of the corner circle that's clamped to half the side width, but rather than affected segment itself. So in this example, the A corner can only cut in to x=5 at most (radius of about .5), while the C and B corners can only cut in to y=0 at most (also radius of about .5). Now I'm doing some dang geometry to try and make a demonstration, and it's hard ;_; |
It may be better to think in terms of clamping the position of the center of the circle. The center must always lie on the angle bisector of the corner, typically at a distance However, we would clamp this distance by both midpoints of the sides, projected (perpendicularly to the sides) towards the angle bisector. So if the sides have lengths And then the clamped radius is I'm not sure if that's the best approach (it might be too strict), but it should work. |
@Loirooriol Would it prevent this problem if we clamp to One question to decide is whether we clamp all circles to the smallest of the radii (this is what |
I'm not certain how that would help. The position of the circle can vary a decent bit, relative to where it touches the sides, depending on the angle of the corner. The thing we want to avoid is one corner's circle running into another; every circle cap should have a (possibly zero-length) straight segment between it and the next. Thus my "ensure it can't go past the halfway point of its adjacent sides" rule; this ensures that two adjacent corners will, at worst, meet at the center of their mutual side. It might be that the trig you worked thru ends up producing that result, but I'm not sure without doing some diagrams by hand. ^_^ |
I think this would be best answered by staring at some examples and seeing what looks good. In all your example images the corners were rounded fairly small compared to the shape, so it's not clear what would look right in more extreme cases. |
That’s not a coincidence, the vast majority of cases I’ve found need fairly small rounding. However looking at them again, eg in the speech bubble, the rounding of the corners should not really be affected by the size of the pointer. So I think per-corner makes the most sense. Also in that case you’d need to be able to override it there anyway. |
I don't see the reasoning of that formula. In particular sqrt makes values between 0 and 1 larger, making it easier to be problematic.
I'm leaning towards clamping independently
When I wrote the comment it seemed simpler to think in those terms to "ensure it can't go past the halfway point of its adjacent sides". But thinking again, using tan() to express the tangent point limits in terms of the radius is more straightforward. Basically, the provided radius needs to be clamped to not exceed |
So basically the upper bound per angle would be A couple other things to explore:
|
It's worth noting that this clamping (done independently per corner) doesn't guarantee that a simple polygon will stay as a non-intersecting shape: |
The polygon may no longer be simple, but it's still well-defined. I think the correct answer for the author is just "don't round that shape, then". We shouldn't guess too hard at what people want in such odd situations. |
The CSS Working Group just discussed
The full IRC log of that discussion |
I like this feature, I made a try using the Paint API: https://css-tricks.com/exploring-the-css-paint-api-rounding-shapes/ where I also considered the case of one radius per corner. It works pretty well and I was able to get some cool examples. |
Great stuff! Not sure if this is something you have the cycles for, but it would be very useful to extract the code that does the rounding into code that we can use to experiment with the algorithm for |
This demo should contain the final code of the API with a lot of examples: https://codepen.io/t_afif/pen/GREaoMJ There are a lot of code to extract the points and their radii from the variables (and also compute /* we start the path */
ctx.beginPath();
ctx.moveTo(Cpoints[0][0],Cpoints[0][1]);
/**/
var i;
var rr;
for (i = 0; i < (Cpoints.length - 1); i++) { /* loop all the points */
/* I start with a complex calculation to avoid the cases illustrated by @Loirooriol and find a maximum radius */
var angle = Math.atan2(Cpoints[i+1][1] - Ppoints[i][1],
Cpoints[i+1][0] - Ppoints[i][0]) -
Math.atan2(Cpoints[i][1] - Ppoints[i][1],
Cpoints[i][0] - Ppoints[i][0]);
if (angle < 0) {
angle += (2*Math.PI)
}
if (angle > Math.PI) {
angle = 2*Math.PI - angle
}
var distance = Math.min(Math.sqrt((Cpoints[i+1][1] - Ppoints[i][1]) ** 2 +
(Cpoints[i+1][0] - Ppoints[i][0]) ** 2),
Math.sqrt((Cpoints[i][1] - Ppoints[i][1]) ** 2 +
(Cpoints[i][0] - Ppoints[i][0]) ** 2));
/**/
rr = Math.min(distance * Math.tan(angle/2),Radius[i]); /* I use a min function to get either the specified radius or the maximum allowed */
ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[i+1][0],Cpoints[i+1][1], rr); /* I draw the curve here */
}
/* an extra curve to get back to the initial point*/
var angle = Math.atan2(Cpoints[0][1] - Ppoints[i][1],
Cpoints[0][0] - Ppoints[i][0]) -
Math.atan2(Cpoints[i][1] - Ppoints[i][1],
Cpoints[i][0] - Ppoints[i][0]);
if (angle < 0) {
angle += (2*Math.PI)
}
if (angle > Math.PI) {
angle = 2*Math.PI - angle
}
var distance = Math.min(Math.sqrt((Cpoints[0][1] - Ppoints[i][1]) ** 2 +
(Cpoints[0][0] - Ppoints[i][0]) ** 2),
Math.sqrt((Cpoints[i][1] - Ppoints[i][1]) ** 2 +
(Cpoints[i][0] - Ppoints[i][0]) ** 2));
rr = Math.min(distance * Math.tan(angle/2),Radius[i]);
ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[0][0],Cpoints[0][1], rr);
/* end of the path*/
ctx.closePath(); cc @Loirooriol |
This is something I've found myself needing often, both when it comes to the SVG Back in 2016, I even wrote a polyfill that did this for SVG This is a demo illustrating rounding regular polygons https://codepen.io/thebabydino/pen/MbxQmJ The same idea was applied for rounding any When it comes to CSS, I've used a bunch of tactics. A very common one has been simply approximating the corner rounding with extra Here is a Sass mixin doing that (not sure if there isn't a newer one) https://codepen.io/thebabydino/pen/dymLJGo/a343189aa330aee3df89883a3215a91d This is an example of a demo using something of the kind https://codepen.io/thebabydino/pen/wvmRXeM/c5f191a40fae4feb587d9e3245d88889 In other instances, I've used a In some particular cases, I've used CSS gradient masks for the effect https://codepen.io/thebabydino/pen/NWXbYwO - this is obviously very limited. |
I’ve added some spec text in ce3f2ac Please take a look and see if it is correct and sufficient. I still need to add examples (@Loirooriol could I use your diagram in #9843 (comment)?) |
@astearns Sure! |
There is support in Figma for setting rounded corners directly on polygons, and there will be an algorithm to limit the maximum value. output.mp4 |
Uh oh!
There was an error while loading. Please reload this page.
Motivation
There is a wide variety of use cases where authors want to clip or mask an element based on a shape that is basically a simple polygon plus rounding. Right now this is exceedingly complicated to do by hand (since you'd need to use
path()
), and practically impossible to do with the same flexibility of units thatpolygon()
supports.Tons of SO questions about this:
It also reminded me of my W3Conf 2013 site design:

The proposal
A good chunk of use cases only requires a single radius for all corners, and I have yet to see a use case that requires different horizontal and vertical radii. Therefore, even something as simple as the following would go a long way.
Change
polygon()
grammar from:to
To cover even more use cases we could also explore allowing
[round]?
on a per-point basis, to customize rounding for a specific point. This can ship later.Rendering
There is always exactly one circle of a given radius that is tangential to each side adjacent to a corner < 180deg.
For corners >= 180deg, the other side would be used:
The text was updated successfully, but these errors were encountered: