Exploring the CSS Paint API: Rounding Shapes
Including borders to complicated shapes is a ache, however rounding the nook of complicated shapes is a nightmare! Fortunately, the CSS Paint API is right here to the rescue! That’s what we’re going to have a look at as a part of this “Exploring the CSS Paint API” series.
Exploring the CSS Paint API collection:
- Half 1: Image Fragmentation Effect
- Half 2: Blob Animation
- Half 3: Polygon Border
- Half 4: Rounding Shapes (you might be right here!)
Right here’s what we’re aiming for. Like every little thing else we’ve checked out on this collection, word that solely Chrome and Edge assist this for now.

You’ll have observed a sample forming should you’ve adopted together with the remainder of the articles. Basically, after we work with the CSS Paint API:
- We write some fundamental CSS that we will simply regulate.
- All of the complicated logic is finished behind the scene contained in the
paint()
perform.
We are able to truly do that with out the Paint API
There are in all probability numerous methods to place rounded corners on complicated shapes, however I’ll share with you three strategies I’ve utilized in my very own work.
I already hear you saying: In the event you already know three strategies, then why are you utilizing the Paint API?
Good query. I’m utilizing it as a result of the three strategies I’m conscious of are tough, and two of them are particularly associated to SVG. I’ve nothing in opposition to SVG, however a CSS-only resolution makes factor simpler to take care of, plus it’s simpler for somebody to stroll into and perceive when grokking the code.
Onto these three strategies…
clip-path: path()
Utilizing In case you are an SVG guru, this methodology is for you. the clip-path
property accepts SVG paths. Meaning we will simply cross within the path for a fancy rounded form and be accomplished. This strategy is tremendous straightforward if you have already got the form you need, nevertheless it’s unsuitable if you’d like an adjustable form the place, for instance, you need to regulate the radius.
Under an instance of a rounded hexagon form. Good luck making an attempt to regulate the curvature and the form dimension! You’re gonna need to edit that crazy-looking path to do it.
I suppose you may seek advice from this illustrated guide to SVG paths that Chris put collectively. But it surely’s nonetheless going to be numerous work to plot the factors and curves simply the way you need it, even referencing that information.
Utilizing an SVG filter
I found this method from Lucas Bebber’s post about creating a gooey effect. You will discover all of the technical particulars there, however the concept is to use an SVG filter to any component to spherical its corners.
We merely use clip-path
to create the form we wish then apply the SVG filter on a father or mother component. To manage the radius, we regulate the stdDeviation
variable.
This can be a good approach, however once more, it requires a deep degree of SVG know-how to make changes on the spot.
Utilizing Ana Tudor’s CSS-only strategy
Sure, Ana Tudor discovered a CSS-only approach for a gooey impact that we will use to not far away of complicated shapes. She’s in all probability writing an article about it proper now. Till then, you possibly can seek advice from the slides she made where she explain how it works.
Under a demo the place I’m changing the SVG filter along with her approach:
Once more, one other neat trick! However so far as being straightforward to work with? Not a lot right here, both, particularly if we’re contemplating extra complicated conditions the place we want transparency, pictures, and so forth. It’s work discovering the proper mixture of filter
, mix-blend-mode
and different properties to get issues excellent.
Utilizing the CSS Paint API as an alternative
Except you may have a killer CSS-only solution to put rounded borders on complicated shapes that you simply’re preserving from me (share it already!), you possibly can in all probability see why I made a decision to succeed in for the CSS Paint API.
The logic behind this depends on the identical code construction I used in the article covering the polygon border. I’m utilizing the --path
variable that defines our form, the cc()
perform to transform our factors, and some different tips we’ll cowl alongside the way in which. I extremely suggest studying that article to raised perceive what we’re doing right here.
First, the CSS setup

We first begin with a traditional rectangular component and outline our form contained in the --path
variable (form 2 above). The --path
variable behaves the identical means as the trail we outline inside clip-path: polygon()
. Use Clippy to generate it.
.field { show: inline-block; peak: 200px; width: 200px; --path: 50% 0,100% 100%,0 100%; --radius: 20px; -webkit-mask: paint(rounded-shape);}
Nothing complicated thus far. We apply the customized masks and we outline each the --path
and a --radius
variable. The latter will likely be used to manage the curvature.
Subsequent, the JavaScript setup

Along with the factors outlined by the trail variable (pictured as crimson factors above), we’re including much more factors (pictured as inexperienced factors above) which might be merely the midpoints of every phase of the form. Then we use the arcTo()
perform to construct the ultimate form (form 4 above).
Including the midpoints is fairly straightforward, however utilizing arcTo()
is a bit difficult as a result of we’ve got to know the way it works. According to MDN:
[It] provides a round arc to the present sub-path, utilizing the given management factors and radius. The arc is robotically related to the trail’s newest level with a straight line, if crucial for the required parameters.
This methodology is usually used for making rounded corners.
The truth that this methodology requires management factors is the principle motive for the additional midpoints factors. It additionally require a radius (which we’re defining as a variable known as --radius
).
If we proceed studying MDN’s documentation:
A method to consider
arcTo()
is to think about two straight segments: one from the place to begin to a primary management level, and one other from there to a second management level. With outarcTo()
, these two segments would kind a pointy nook:arcTo()
creates a round arc that matches this nook and smooths is out. In different phrases, the arc is tangential to each segments.
Every arc/nook is constructed utilizing three factors. In the event you verify the determine above, discover that for every nook we’ve got one crimson level and two inexperienced factors on all sides. Every red-green mixture creates one phase to get the 2 segments detailed above.
Let’s zoom into one nook to raised perceive what is occurring:

The circle in blue illustrates the radius.
Now think about that we’ve got a path that goes from the primary inexperienced level to the following inexperienced level, shifting round that circle. We do that for every nook and we’ve got our rounded form.
Right here’s how that appears in code:
// We first learn the variables for the trail and the radius.const factors = properties.get('--path').toString().break up(',');const r = parseFloat(properties.get('--radius').worth);var Ppoints = [];var Cpoints = [];const w = dimension.width;const h = dimension.peak;var N = factors.size;var i;// Then we loop by way of the factors to create two arrays.for (i = 0; i < N; i++) { var j = i-1; if(j<0) j=N-1; var p = factors[i].trim().break up(/(?!(.*)s(?![^(]*?))/g); // One defines the crimson factors (Ppoints) p = cc(p[0],p[1]); Ppoints.push([p[0],p[1]]); var pj = factors[j].trim().break up(/(?!(.*)s(?![^(]*?))/g); pj = cc(pj[0],pj[1]); // The opposite defines the inexperienced factors (Cpoints) Cpoints.push([p[0]-((p[0]-pj[0])/2),p[1]-((p[1]-pj[1])/2)]);}/* ... */// Utilizing the arcTo() perform to create the formctx.beginPath();ctx.moveTo(Cpoints[0][0],Cpoints[0][1]);for (i = 0; i < (Cpoints.size - 1); i++) { ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[i+1][0],Cpoints[i+1][1], r);}ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[0][0],Cpoints[0][1], r);ctx.closePath();/* ... */ctx.fillStyle="#000";ctx.fill();
The final step is to fill our form with a stable coloration. Now we’ve got our rounded form and we will use it as a masks on any component.
That’s it! Now all we’ve got to do is to construct our form and management the radius like we wish — a radius that we will animate, because of @property
which is able to make issues extra attention-grabbing!

Are there any drawbacks with this methodology?
Sure, there are drawbacks, and also you in all probability observed them within the final instance. The primary downside is expounded to the hover-able space. Since we’re utilizing masks
, we will nonetheless work together with the preliminary rectangular form. Bear in mind, we confronted the identical problem with the polygon border and we used clip-path
to repair it. Sadly, clip-path
doesn’t assist right here as a result of it additionally impacts the rounded nook.
Let’s take the final instance and add clip-path
. Discover how we’re shedding the “inward” curvature.
There’s no problem with the hexagon and triangle shapes, however the others are lacking some curves. It might be an attention-grabbing characteristic to maintain solely the outward curvature — because of clip-path
— and on the similar time we repair the hover-able space. However we can not preserve all of the curvatures and cut back the hover-able space on the similar time.
The second problem? It’s associated to using a giant radius worth. Hover over the shapes beneath and see the loopy outcomes we get:
It’s truly not a “main” downside since we’ve got management over the radius, nevertheless it positive can be good to keep away from such a state of affairs in case we wrongly use a very giant radius worth. We might repair this by limiting the worth of the radius to inside a spread that caps it at a most worth. For every nook, we calculate the radius that permits us to have the most important arc with none overflow. I received’t dig into the maths logic behind this (😱), however right here is the ultimate code to cap the radius worth:
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) );var rr = Math.min(distance * Math.tan(angle/2),r);
r
is the radius we’re defining and rr
is the radius we’re truly utilizing. It equal both to r
or the utmost worth allowed with out overflow.
In the event you hover the shapes in that demo, we now not get unusual shapes however the “most rounded form” (I simply coined this) as an alternative. Discover that the common polygons (just like the triangle and hexagon) logically have a circle as their “most rounded form” so we will have cool transitions or animations between completely different shapes.
Can we’ve got borders?
Sure! All we’ve got to do is to make use of stroke()
as an alternative of fill()
inside our paint()
perform. So, as an alternative of utilizing:
ctx.fillStyle="#000";ctx.fill();
…we use this:
ctx.lineWidth = b;ctx.strokeStyle="#000";ctx.stroke();
This introduces one other variable, b
, that controls the border’s thickness.
Did you discover that we’ve got some unusual overflow? We confronted the identical problem within the earlier article, and that as a result of how stroke()
works. I quoted MDN in that article and can do it once more right here as nicely:
Strokes are aligned to the middle of a path; in different phrases, half of the stroke is drawn on the internal aspect, and half on the outer aspect.
Once more, it’s that “half internal aspect, half outer aspect” that’s getting us! With a view to repair it, we have to conceal the outer aspect utilizing one other masks, the primary one the place we use the fill()
. First, we have to introduce a conditional variable to the paint()
perform so as to select if we need to draw the form or solely its border.
Right here’s what we’ve got:
if(t==0) { ctx.fillStyle="#000"; ctx.fill();} else { ctx.lineWidth = 2*b; ctx.strokeStyle="#000"; ctx.stroke();}
Subsequent, we apply the primary kind of masks (t=0
) on the principle component, and the second kind (t=1
) on a pseudo-element. The masks utilized on the pseudo-element produces the border (the one with the overflow problem). The masks utilized on the principle component addresses the overflow problem by hiding the outer a part of the border. And should you’re questioning, that’s why we’re including twice the border thickness to lineWidth
.

See that? We have now excellent rounded shapes as outlines and we will regulate the radius on hover. And may use any form of background on the form.
And we did all of it with a little bit of CSS:
div { --radius: 5px; /* Defines the radius */ --border: 6px; /* Defines the border thickness */ --path: /* Outline your form right here */; --t: 0; /* The primary masks on the principle component */ -webkit-mask: paint(rounded-shape); transition: --radius 1s;}div::earlier than { content material: ""; background: ..; /* Use any background you need */ --t: 1; /* The second masks on the pseudo-element */ -webkit-mask: paint(rounded-shape); /* Take away this if you'd like the total form */}div[class]:hover { --radius: 80px; /* Transition on hover */}
Let’s not neglect that we will simply introduce dashes utilizing setLineDash()
the identical means we did within the earlier article.

Controlling the radius
In all of the examples we’ve checked out, we at all times contemplate one radius utilized to all of the corners of every form. It will be attention-grabbing if we might management the radius of every nook individually, the identical means the border-radius
property takes as much as 4 values. So let’s prolong the --path
variable to contemplate extra parameters.
Truly, our path might be expressed as an inventory of [x y]
values. We’ll make an inventory of [x y r]
values the place we introduce a 3rd worth for the radius. This worth isn’t obligatory; if omitted, it falls again to the principle radius.
.field { show: inline-block; peak: 200px; width: 200px; --path: 50% 0 10px,100% 100% 5px,0 100%; --radius: 20px; -webkit-mask: paint(rounded-shape);}
Above, we’ve got a 10px
radius for the primary nook, 5px
for the second, and since we didn’t specify a worth for the third nook, it inherits the 20px
outlined by the --radius
variable.
Right here’s our JavaScript for the values:
var Radius = [];// ...var p = factors[i].trim().break up(/(?!(.*)s(?![^(]*?))/g);if(p[2]) Radius.push(parseInt(p[2]));else Radius.push(r);
This defines an array that shops the radius of every nook. Then, after splitting the worth of every level, we take a look at whether or not we’ve got a 3rd worth (p[2]
). If it’s outlined, we use it; if not, we use the default radius. In a while, we’re utilizing Radius[i]
as an alternative of r
.

This minor addition is a pleasant characteristic for after we need to disable the radius for a selected nook of the form. The truth is, let’s take a look at just a few completely different examples subsequent.
Extra examples!
I made a collection of demos utilizing this trick. I like to recommend setting the radius to 0 to raised see the form and perceive how the trail is created. Do not forget that the --path
variable behaves the identical means as the trail we outline inside clip-path: polygon()
. In the event you’re on the lookout for a path to play with, try using Clippy to generate one for you.
Instance 1: CSS shapes
Lots of fancy shapes might be created utilizing this method. Listed below are just a few of them accomplished with none further parts, pseudo-elements, or hack-y code.
Instance 2: Speech bubble
In the previous article, we added border to a speech bubble component. Now we will enhance it and around the corners utilizing this new methodology.
In the event you evaluate with this instance with the original implementation, chances are you’ll discover the very same code. I merely made two or three modifications to the CSS to make use of the brand new Worklet.
Instance 3: Frames
Discover beneath some cool frames in your content material. No extra complications after we want gradient borders!
Merely play with the --path
variable to create your individual responsive body with any coloration your need.
Instance 4: Part divider
SVG is now not wanted to create these wavy part dividers which might be well-liked today.
Discover that the CSS is mild and comparatively easy. I solely up to date the trail to generate new cases of the divider.
Instance 5: Navigation menu
Right here’s a traditional design sample that I’m positive many people have ran into at a while: How on earth can we invert the radius? You’ve possible seen it in navigation designs.
A barely completely different tackle it:
Instance 6: Gooey impact
If we play with the trail values we will attain for some fancy animation.
Under an concept the place I’m making use of a transition to just one worth of the trail and but we get a fairly cool impact
This one’s impressed by Ana Tudor’s demo.
One other concept with a special animation
One other instance with a extra complicated animation:
What a few bouncing ball
Instance 7: Form morphing
Taking part in with large radius values permits us to create cool transitions between completely different shapes, particularly between a circle and an everyday polygon.
If we add some border animation, we get “respiratory” shapes!
Let’s spherical this factor up
I hope you’ve loved getting nerdy with the CSS Paint API. All through this collection, we’ve utilized paint()
to a bunch of real-life examples the place having the API permits us to control parts in a means we’ve by no means been in a position to do with CSS — or with out resorting to hacks or loopy magic numbers and whatnot. I actually consider the CSS Paint API makes seemingly difficult issues lots simpler to unravel in an easy means and will likely be a characteristic we attain for repeatedly. That’s, when browser assist catches as much as it.
In the event you’ve adopted together with this collection, and even simply stumbled into this one article, I’d like to know what you consider the CSS Paint API and the way you think about utilizing it in your work. Are there any present design tendencies that may profit from it, just like the wavy part dividers? Or blobby designs? Experiment and have enjoyable!
This one’s taken from my previous article
Exploring the CSS Paint API collection:
- Half 1: Image Fragmentation Effect
- Half 2: Blob Animation
- Half 3: Polygon Border
- Half 4: Rounding Shapes (you might be right here!)
Checkout extra Articles on Sayed.CYou
Comments
Post a Comment