Learn Development at Frontend Masters

*The following is a guest post by Ana Tudor. Perhaps you know Ana from her amazing work combining code, math, and art. Here, she shows us how we can change the normal behavior of clipping paths by applying some clever geometry, and then make it work across different technology and browsers.*

The `clip-path`

property in CSS is used to hide areas of an element *outside* the path. But we can also cut out an area *inside* the element this way. The same can be achieved with masking by reversing the fill colors, but what I’m aiming for here is to show how this effect can be achieved with `clip-path`

.

The path needs to have two parts:

- an outer one which makes sure the area inside it stays visible
- a inner one in order to remove a part of the area inside the outer path

The two need to be connected by a **zero width tunnel**. No matter what the eyes think they see, everything is still inside of the path.

The demo below shows how the path is drawn and how expanding the width of the tunnel reveals that what *looks like* a border is actually the area inside the path. Note how the outer part is drawn counterclockwise, while the inner one is being drawn clockwise.

Alright, let’s see how we can CSS this. The only basic shape we can use is `polygon()`

. So we’re going to have something like this:

```
clip-path: polygon(
/* points of the outer triangle going counterclockwise */
285px 150px, 83px 33px, 83px 267px,
/* return to the first point of the outer triangle */
285px 150px,
/* points of the inner triangle going clockwise */
258px 150px, 96px 244px, 96px 56px,
/* return to the first point of the inner triangle */
258px 150px
);
```

You can see it working in this pen (WebKit/Blink browsers and Firefox 47+ with `layout.css.clip-path-shapes.enabled`

set to `true`

in `about:config`

):

That’s a lot of code to write manually. And there is a lot more code if, instead of a triangle, we want to have a dodecagon. Which is why we can use Sass to automate the generation of the list of points for regular polygons (polygons having all edges equal and all vertices equal).

Let’s see how that works.

As you can see in the demo below, all vertices of a regular polygon are situated on a circle called the circumcircle.

See the Pen in/circumcircle of a regular polygon (SVG version) by Ana Tudor (@thebabydino) on CodePen.

In order to get a regular polygon with `n`

edges/vertices, we need to evenly distribute the `n`

vertices on a circle and then connect the first with the second, the second with the third and so on, until we connect the last one back to the first. But how do we distribute the points on the circle?

The position of a point on a circle can be described by the radius of the circle and by the angle formed between the radius connecting the point to the centre of the circle and the radius along the `x`

axis in the positive direction.

As the image above shows, the `x`

coordinate is the radius times the cosine of angle it forms with the horizontal. The `y`

coordinate is the radius times the sine of the same angle. We’ll take the outer radius to be an arbitrary value that’s at most half the minimum dimension of the element we want clipped and the inner one somewhat smaller. But what is the angle for each vertex?

If we place the first vertex at angle `0`

, we distribute all vertices evenly on the circle and there are `360`

degrees around the circle, then the angle corresponding to the `i`

th vertex of a polygon with `n`

vertices in total is going to be `i*360deg/n`

.

So our Sass code starts out by setting a value greater or equal to `3`

for the number of vertices, computing the base angle (`360deg/n`

), creating the outer and inner radius variables, the x and y offset variables – these will specify the position of the circumcentre – and the empty point lists for both the inner and the outer triangles.

```
$n: 3;
$base-angle: 360deg/n;
$r-outer: 150px;
$r-inner: 120px;
$offset-x: 50%;
$offset-y: 50%;
$points-inner: ();
$points-outer: ();
```

We then loop through the vertices, compute the coordinates for each and add them to the corresponding list:

```
@for $i from 0 through $n {
$points-outer: append(
/* list of points for the outer polygon*/
$points-outer,
/* x coordinate of current outer vertex */
calc(#{$offset-x} + #{$r-outer*cos(-$i*$base-angle)})
/* y coordinate of current outer vertex */
calc(#{$offset-y} + #{$r-outer*sin(-$i*$base-angle)}),
comma) !global;
$points-inner: append(
/* list of points for the inner polygon*/
$points-inner,
/* x coordinate of current inner vertex */
calc(#{$offset-x} + #{$r-inner*cos($i*$base-angle)})
/* y coordinate of current inner vertex */
calc(#{$offset-y} + #{$r-inner*sin($i*$base-angle)}),
comma) !global;
}
```

There is a few important things here to consider. First, when looping, we have `$i from 0 through $n`

, meaning we add `$n + 1`

sets of coordinates to our lists. This is not a mistake, because for both the outer and the inner polygon, we need to return to the first vertex to connect them through the zero width tunnel. Second, the current angle for each vertex is `-$i*$base-angle`

in the case of the outer polygon and `$i*$base-angle`

in the case of the inner polygon. This is because the outer one goes counterclockwise and the inner one clockwise.

At this point, we have two lists containing the vertices of the outer polygon and those of the inner polygon respectively. Now all we need to do is to join them when putting them inside the polygon function.

`clip-path: polygon(join($points-outer, $points-inner));`

You can play with it live in this Pen. Change the values of `$n`

to see how the polygon changes (WebKit/Blink browsers and Firefox 47+ with `layout.css.clip-path-shapes.enabled`

set to `true`

in `about:config`

):

For Firefox, we need to use a reference to an SVG `<clipPath>`

element. We would put this in the HTML:

```
<svg width='0' height='0'>
<defs>
<clipPath id='cp'>
<path d='M285,150 L83,33 L83,267
L285,150
L258,150 L96,244 L96,56
L258,150z' />
</clipPath>
</defs>
</svg>
```

Here, the first line of the path data contains the coordinates of the outer polygon vertices, the second one the coordinates of its first vertex, the third one contains the coordinates of the inner polygon vertices and the fourth one repeats the coordinates of the first vertex of this second polygon.

And in the CSS we’ll simply have:

`clip-path: url(#cp);`

This works in Firefox now, as you can see in this demo:

However, there are a number of things to keep in mind here.

We cannot mix units like we can inside the `polygon()`

function. The numbers in the path data are hardcoded and not flexible at all. Also, as it was noted before, Chrome/ Opera take the `0,0`

point for our `clip-path`

to be the top left of the screen, not the top left of the element we want to clip. We can get past this by changing the value of the `clipPathUnits`

attribute from `userSpaceOnUse`

(the default) to `objectBoundingBox`

. Doing this forces us to change the values in the path, which should now all be scaled to the `[0, 1]`

interval. Changing transforms on the clipped element is still buggy in Chrome:

As for making this flexible, all we need to do is recreate with JavaScript what we have done with Sass.

```
var n = 3,
base_angle = 2*Math.PI/n,
r_outer = .5,
r_inner = .4,
offset_x = .5,
offset_y = .5,
points_outer = '',
points_inner = '',
angle, x, y;
for(var i = 0; i <= n; i++) {
angle = i*base_angle;
x = Math.cos(angle);
y = Math.sin(angle);
points_outer += ((i === 0)?'M':' L') +
(offset_x + r_outer*x).toFixed(3) + ', ' +
(offset_y - r_outer*y).toFixed(3);
points_inner += ' L' +
(offset_x + r_inner*x).toFixed(3) + ', ' +
(offset_y + r_inner*y).toFixed(3);
}
document.querySelector('#cp path').setAttribute('d', points_outer + points_inner + 'z');
```

If you’re wondering why we have `- r_outer*y`

while the other three similar terms are with `+`

, it’s again because of the fact that the outer and inner polygons have to be drawn in opposite directions (chosen here to be counterclockwise for the outer polygon and clockwise for the inner one). This means we should use `-angle`

at each step to compute the positions of the vertices for the outer polygon and `angle`

for the inner one. But `cos(angle) = cos(-angle)`

(same sign), while `sin(angle) = -sin(-angle)`

(opposite signs). Since we’ve stored `cos(angle)`

in `x`

and `sin(angle)`

in `y`

, we have the coordinates of the current point relative to the offset: `r_outer*x, r_outer*-y`

for the outer polygon and `r_inner*x, r_inner*y`

for the inner one.

You can play with it all in this Pen (changing the value of the `n`

variable changes the entire polygon):

If you want to get the same visual result in IE as well, then the clipped element should be an SVG element. The initial markup becomes:

```
<svg width='300' height='300'>
<defs>
<clipPath id='cp' clipPathUnits='objectBoundingBox'>
<path d='' />
</clipPath>
</defs>
<rect class='clip-me' width='300' height='300'/>
</svg>
```

And you can play with it in this Pen:

There are more things you can use the zero width tunnel method for. One example is cutting out an area in the middle of an element.

Or you could use it to clip out everything with the exception of a few unconnected areas. This is pretty simple to achieve anyway when using a reference to a `<clipPath>`

element, but a zero width tunnel between regions is the only way to get this result when using the `polygon()`

value in CSS.

And of course, there’s always the option of multiple polygons with the inside cut out.

Wow, each time I see stuff like this, it amazes me more and more what is possible through CSS alone in the nearby future.

And I can slam my head more and more too, that I didn’t paid enough attention at mathematics class to really understand all this CSS kung-fu.

Me too…does anyone know of a treehouse like site where one can go and brush up on their rusty math skills…After some recent posts here I feel like I need to really brush up on my math to stay in the game…

Oh great post as well…

@Michael Whyte, have you looked at KhanAcademy.org? It’s free.

Even though I do front-end coding for a living, after seeing this I could now be asked the question “Do you know anything about CSS?” on polygraph and pass truthfully saying “Nope!”

Mind blown! Phenomenal work.

Likewise! I see articles like these and my confidence in myself as a web designer/developer goes way down. Ha!

Ana Tudor is at it again. I have a list of your collections Ana. Big fan here. Although the regular web projects we do wouldn’t need most of this but it still is really awesome to have these in one’s skillset