The following is a guest post by Ryan Scherf. Ryan found a neat way to give avatars kind of rough, uneven, varied edges. Kinda like they were cut out with scissors by someone who wasn’t very good at using scissors. What’s nice is it’s naturally a progressive enhancement technique and it can be done through just CSS.
With a creative and fun brand like Quirky, we are always thinking about ways to bring that vibe to the web. Throughout the site, there is a “hand drawn” look to some elements. Without the use of lots of images, it’s very difficult to get that hand-drawn vibe. With some light trigonometry and very basic knowledge of CSS’ clip-path
, we’re able to do this with relative ease and good performance.

Why not use image masks?
For instance, a mask defined in SVG:
img {
mask: url(mask.svg) top left / cover;
}
The mask
property can reference external SVG or SVG defined in the document by ID.
But what if you wanted a unique shape for every single avatar displayed, not the same shape? You could programmatically generate lots of different SVG shapes to apply. But we can achieve the same thing and get that mathematical generation through generating clip-path
s with (S)CSS.
What’s the browser support?
The browser support for clip-path
, when used with a shape value like polygon()
, is Chrome 24+, Safari 7+, Opera 25+, iOS 7.1+, Android 4.4+. Firefox supports clip-path
only with the path defined in SVG (we’ll cover that). No support in IE yet.
You’ll need to use -webkit-clip-path
, as that’s the only way it’s supported right now, but probably best to drop clip-path
on there too. If IE or Firefox start supporting it this way, it’ll likely be unprefixed.
Clipping paths in a nutshell
There are a few different shape values you can use for CSS clipping but in our case, the polygon
shape is best as it gives us the most amount of points and flexibility to create our hand-drawn effect.
You give polygon()
a list of X, Y point values, like: <x0> <y0>, <x1> <y1>, ... <xn> <yn>
. That will draw a path around your points in order and crop any of the content outside of the newly created shape.
/*
This will create a Hexagon, with the first
point being the top tip of the shape
*/
.hexagon {
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
Here is that simple example in action:
See the Pen Hexagon with clip-path by Chris Coyier (@chriscoyier) on CodePen.
Not-so-scary math
Our hexagon is pretty cool, but it doesn’t achieve a real sketchy effect quite yet. It’s quite rigid – too few lines. The best way to think of a hand-drawn shape is a series of small lines connecting two dots. The more dots we have, the more short lines we create. In fact, with enough points, we could make a polygon
shape so smooth it mimics a circle
.
Here is an example of using 200 points:
See the Pen 200 Points by Chris Coyier (@chriscoyier) on CodePen.
Where do the points come from?
Here’s where a little bit of math comes in. Perhaps you took trigonometry in high school? One of the fundamental ideas you learn in that class is regarding the Unit Circle. Basically, there is a set formula (given pi) that can generate any number of points around a circle.

If we were to connect our segments, we’d get a shape that looked like:

Still a little rigid, but looking a little more hand-drawn as well.
More Points!
We know how to make hexagons and circles with the clip-path: polygon()
, so how do we make it look hand-drawn?
- Adjust the number of points (the more there are, the lower the segment lengths)
- Add some X and Y variance (so the segments aren’t uniform)
Let’s bring that in SCSS and create a function to do the dirty work for us. We’ll be using:
random()
cos()
sin()
The most relevant math is:
/*
To generate an arbitrary points on
the unit circle at angle t
*/
$x: cos(t);
$y: sin(t);
And putting that in the right syntax looks like:
$w: 160px // Avatar width
$n: 60; // Number of points on the circle
@function sketchAvatar() {
$points: ();
@for $i from 0 through $n {
$points: append($points, ($w / 2) * (1 + cos((2 * pi() * $i / $n))) ($w / 2) * (1 + sin((2 * pi() * $i / $n))), comma);
}
@return $points;
}
This is a little hairy. What is happening is we start at the top middle of our shape, and generate list of sets of points around the circle for 60 evenly spaced points.
Bringing it altogether with variances
The above code still produces fairly bland and uniform polygons, so we’ll have to add in variance. All we need to do is adjust the points in any direction to give that offset feel we’re looking for. The $lower
and $upper
variance numbers can be just about anything depending on the look you’re going for.
$w: 120px; // Overall width
@function sketchAvatar() {
$n: 60; // Number of points
$lower: -80; // Lower variance
$upper: 80; // Upper variance
$points: ();
@for $i from 0 through $n {
$points: append($points, ($w / 2) * (1 + cos((2 * pi() * $i / $n))) + (rand($lower, $upper) / 100) ($w / 2) * (1 + sin((2 * pi() * $i / $n))), comma);
}
@return $points;
}
We did it! Sketchy, unique avatars with CSS clip-path: polygon()
:
See the Pen Sketchy Avatars by Chris Coyier (@chriscoyier) on CodePen.
Making it work in Firefox
Chris here! I thought since Firefox doesn’t support this done this way, but does support the SVG syntax, we could maybe kinda polyfill it.
.avatar {
clip-path: polygon( ... ) /* Firefox: nope */
clip-path: url(#clip); /* Firefox: yep */
}
So for each avatar, I…
- Output the polygon points in the content property of a pseudo element (of an element that has a valid pseudo element like the parent div) in CSS
- Extracted that value with JavaScript
- Reformat the points to match the SVG format (e.g. no “px”)
- Injected a new
<svg>
on the path with a<clipPath>
ready to go
$(".user").each(function(i) {
var path = window.getComputedStyle(this, ':after').getPropertyValue('content');
// clean house
svgPolygonPoints =
path
.replace(/px/g, "")
.replace(/polygon/, "")
.replace(/\(/, "")
.replace(/\)/, "")
.replace(/\;/g, "")
.replace(/"/g, "")
.replace(/\'/g, "");
// To get this to actually work, create a <div> instead with this inside, see below.
var svg = $("<svg width='0' height='0'>")
.append("<defs><clipPath id='clip-" + (i+1) +"'><polygon points='" + svgPolygonPoints +"' /></clipPath></defs>");
$("body").append(svg);
});
It doesn’t work! haha. Even if you force a repaint on the avatars, it just doesn’t like the injected SVG for some reason. Check out Amelia’s solution
It’s basically like:
.user:nth-child(1) {
clip-path: polygon(120.04px 60px ...);
}
becomes:
<svg width="0" height="0">
<defs>
<clippath id="clip-1">
<polygon points="120.04 60, ... "></polygon>
</clippath>
</defs>
</svg>
Interessting, but still too complicated for an effect that you can easilly get using transparent pictures.
But if you want all avatars to be uniform, can you really expect all of your users to upload transparent pictures that mask the image correctly? Do you want to be hassled with making sure every time you upload your own avatar that it is masked the way it’s supposed to be? This solution is actually a lot more convenient.
I agree…an interesting proof of concept…but complex…for present day for full cross browser support standard transparent images would be best…but for the future this is something to look at going forward…
This and also CSS masks…look promising…but again FireFox only supports inline SVG as a mask source while the others (Chrome, Opera, Safari) can take an image mask in a PNG or JPG format…and again IE no support at all… (source: http://caniuse.com/#feat=css-masks ) …
Interesting post…thanks for sharing…
One possible usecase for this would be to use the same created polygons as a CSS Shape on the picture to make some text flow around it, which you can’t do with a transparent picture.
Like so:
See the Pen MYYWrg by Karsten Buckstegge (@MrBambule) on CodePen.
I like the comeback of CSS Tricks here. For a while things got pretty serious around here, not as many fun-but-only-vaguely-useful articles. Good to have the tricks back! :)
It doesn’t work because you’re using JQuery. JQuery doesn’t know how to create elements in the SVG namespace, or which elements should be in the SVG namespace. Which means that it creates a bunch of elements for you which look correct in your DOM inspector — they have the correct tag names — but they are actually all
HTMLUnknownElement
in the DOM. And so you can’t actually use them as SVG content.In contrast, when you copy and paste that markkup into a separate file, that file will get read by the HTML5 parser, which will recognize all <svg> elements and their children as being, well,
SVGElement
s.You have two options:
document.createElementNS("www.w3.org/2000/svg", "svg")
(and the same for their children)..innerHTML
property of the div element, or the.append()
JQuery method, (c) extracting the SVG element from the container div.I use option 2 here. It gets the clip path working in Firefox:
Nice! I’ll chalk that up as the most important thing I learned today.
…also just updated the pen, so that it only injects the SVG elements when they are actually going to be used.
Almost, anyway. It won’t create the SVG if the final clip path polygon function is used (hypothetical so far), or if the webkit-clip-path property is recognized (Chrome or Opera). But it still creates them for Internet Explorer, which recognizes the basic url form of clip-path as a valid style declaration, but doesn’t apply it to non-SVG elements.
This is pretty cool. Unfortunately, 99.9% of users won’t even notice or care that the edges look different. So, you might as well do it the easy way with images/SVG.
Nice .Very interesting , unfortunetely I have never been good in math but it doesn`t look difficult .
Thanks for sharing this .
svgPolygonPoints ist added to the global window object. Not very nice.
btw, if you don’t know there is cross browser clip path generator http://cssplant.com/clip-path-generator
Or your could try http://bennettfeely.com/clippy cough cough
Nice post, good comments. I love calculated images, especially when trigonometry and randomness comes in to play.
I don’t think this is working on Windows 8 on Chrome. I just dragged an image over the other one on codepen and it overlayed identically.
loved it.. nice codding… you make this easy after such an explanation!