-
Notifications
You must be signed in to change notification settings - Fork 699
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
[css-shapes] Allow optional rounding parameter for polygon()
#9843
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<emilio> lea: this is about resolving whether we should work on this<emilio> ... noticed that even though we have polygon() and clip-path, author still resort to images because they don't cover their use cases, the use cases include rounding <emilio> ... many cases the same as the polygon <emilio> ... I posted many <emilio> ... proposal was to add a round parameter to the polygon, and then to also add an optional on each individual coordinate to customize rounding to that corner <emilio> ... the way it'd be painted would be "a circle of that radius that is tangential to both vertices" <emilio> ... if it's >180deg it'd draw outside <emilio> ... we did some research on the max values to not end up with the right shape <TabAtkins> q+ <astearns> q+ <emilio> ... question is whether there are blockers or should we work on this <fantasai> +1 to the proposal <astearns> ack TabAtkins <emilio> TabAtkins: I agree with this. There are some details about limits <emilio> ... but they don't need to be worked out here. Suggested functionality and syntax sounds good <emilio> q+ <astearns> ack astearns <emilio> ... so very supportive <emilio> astearns: two questions <emilio> ... perfectly fine with the proposal <emilio> ... but I'm a bit concerned about rounding polygon corners because it's no longer a polygon <emilio> ... so I wonder if it'd be better to fix path() to make this syntax simpler <emilio> ... but if we do this I don't mind <emilio> TabAtkins: we already round rectangles <emilio> astearns: the other question is that it'd be nice if the rounding matches the rounding for shape-margin for polygons <emilio> ... there's some text in the shapes spec that says how we do that <emilio> TabAtkins: for expanding shapes it works the same way <emilio> ... when it goes in I'm less sure <astearns> ack emilio <lea> re: is it a polygon any more, e.g. look at the W3Conf example, would you describe these as anything other than hexagons? <dholbert> emilio (IRC): same line as astearns (IRC) ... Do we have anything to make this sort of path easily already right now? <dholbert> TabAtkins (IRC): We do in the canvas specs. It has an operation to do this sort of rounding, corner-by-corner <bkardell_> let's add it to svg :) <dholbert> TabAtkins (IRC): It doesn't exist in SVG, but in the other thread I suggested pulling in some of the canvas operations as well <fantasai> bkardell_, you volunteering to edit SVG? :) <dholbert> emilio (IRC): seems like this use case should be/become covered by path, since it is a path <lea> bkardell_: we'd need an SVG WG first and buy-in from implementers (who are *very* unwilling to implement any SVG thing)... <astearns> ack fantasai <Zakim> fantasai, you wanted to respond to that <dholbert> emilio (IRC): it seems reasonable to add it to polygon, but we should make sure you can do this for a path too <emilio> fantasai: could you do this in path? Yeah, but lots more work <TabAtkins> the canvas `arcTo()` is literally "define a corner + a radius, and add the rounded corner to your path" <emilio> ... by making the assumption that everything is a line it makes it a lot easier in polygon() <TabAtkins> bkardell_, I don't think we need to add it to SVG *per se*, but adding it to shape() is fine <emilio> ... telling authors that they should use path() for this is fine <lea> +1 to fantasai's point, we even have a TAG principle to avoid cliffs where a small amount of use case complexity results in a large increase in complexity <bkardell_> igalia is working on svg. I admit it is not 'new' svg, but if there are people (or organizations) interested in svg they should definitely maybe talk to me :) <emilio> astearns: agree but we should make it easy to move from one to another <lea> s/1 to fantasai's point, we even have a TAG principle to avoid cliffs where a small amount of use case complexity results in a large increase in complexity/1 to fantasai's point, we even have a TAG principle to avoid cliffs where a small amount of use case complexity results in a large increase in UI complexity/ <emilio> PROPOSAL: Work on adding corner rounding to polygon() <emilio> RESOLVED: Work on adding corner rounding to polygon() <fantasai> \^_^/ <emilio> emilio: should we resolve on rounded lines for path()? <emilio> fantasai: there's another issue for that, a bit more complicated |
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 |
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 <length>]?
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: