Both of these things are used to hide some parts of elements and show other parts. But there are, of course, differences between the two. Differences in what they can do, differences in syntaxes, different technologies involved, the new and the deprecated, and browser support differences.
And sadly there is quite a bit of outdated information out there. Let’s see if we can sort it out.
The difference between clipping and masking
Masks are images; Clips are paths.
Imagine a square image that is a left-to-right, black-to-white gradient. That can be a mask. The element it is applied to will be transparent (see-through) where there is black in our gradient mask image, and opaque (normal) where there is white. So the final result will be an element that fades in from left to right.
Clips are always vector paths. Outside the path is transparent, inside the path is opaque.
I personally found this confusing, because often times you’ll run across a tutorial on masking that uses a masking image that a white vector-looking shape on black, so it is basically doing the same thing a clip. That’s fine, it just confuses things a bit.
clip
The Old/Deprecated The first presence of clipping in CSS (other than overflow: hidden;
trickery) was the clip
property. (MDN).
It was like this:
.element {
clip: rect(10px, 20px, 30px, 40px);
}
Those four values are in the same order as margin/padding:
- 10px from the top of the element
- 20px from the right of the element
- 30px from the bottom of the element
- 40px from the left of the element
The clip
property is deprecated in CSS, meaning the use of it isn’t recommended because there is a newer, standardized version that browsers will actually focus their efforts.
There are some strengths of clip: because clip
shipped in browsers, it will probably always work. And the browser support for it is extremely strong: pretty much every browser ever. Plus I’ve heard animation performance from it trumps newer methods.
There are two rather significant weaknesses of clip though, which makes not using it easier to swallow:
clip
only works if the element is absolutely positionedclip
can only do rectangles.
Pretty limiting. So let’s move on.
clip-path
The New The new, recommend version of applying clipping to elements in CSS is clip-path
. You’d think it would be as simple as:
.element {
/* NOPE */
clip-path: rect(10px, 20px, 30px, 40px);
}
That doesn’t work though (even prefixed, anywhere). Eventually, we’ll get rectangle()
, just not yet:
Postponed rectangle() to level 2
The new way to do this is with inset():
.element {
clip-path: inset(10px 20px 30px 40px);
/* Also can take single values to make all sides the same, or 2 values (vert/horz), or 3 values (top/horz/bottom). */
}
Note that there are no commas, and the syntax is different, but ultimately does the same kind of things.
What also does work with clip-path
though (in some browsers), is circles, ellipses, and polygons. Here’s some examples:
.clip-circle {
clip-path: circle(60px at center);
/* OLD VALUE example: circle(245px, 140px, 50px); */
/* Yep, even the new clip-path has deprecated stuff. */
}
.clip-ellipse {
clip-path: ellipse(60px 40px at 75px 30px);
/* OLD VALUE example: ellipse(245px, 80px, 75px, 30px); */
}
.clip-polygon {
clip-path: polygon(5% 5%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%);
/* Note that percentages work as well as px */
}
See the Pen clip-path examples by Chris Coyier (@chriscoyier) on CodePen.
Polygon is pretty powerful. Ryan Scherf posted a tutorial here on CSS-Tricks on using it programmatically to produce a sketchy effect.
I highly recommend playing with it through Bennet Feely’s Clippy:
Theoretically, this is what clip-path
will support (known as “basic shapes“):
.clip-examples {
clip-path: rectangle(x, y, width, height, rounded-x, rounded-y)
clip-path: inset-rectangle(from top, from right, from bottom, from left, rounded-x, rounded-y)
/* Looks like this is what rect() used to be like with clip */
/* Will replace inset(), I suppose */
clip-path: polygon(a, bunch, of, points)
clip-path: circle(radius at x, y)
clip-path: ellipse(radius-x, radius-y at x, y)
}
I can’t seem to find info if path()
will ever be a valid value.
Using clip-path with an SVG-defined <clipPath>
You don’t have to define the clip-path value right in CSS, it can reference a <clipPath>
element defined in SVG. Here’s what that looks like:
<img class="clip-svg" src="harry.jpg" alt="Harry Potter">
<svg width="0" height="0">
<defs>
<clipPath id="myClip">
<circle cx="100" cy="100" r="40" />
<circle cx="60" cy="60" r="40" />
</clipPath>
</defs>
</svg>
.clip-svg {
clip-path: url(#myClip);
}
Demo:
See the Pen clip-path examples by Chris Coyier (@chriscoyier) on CodePen.
Sara Soueidan has a demo of this in action as well.
There is a rather significant issue with using SVG-defined clip paths though: some browsers pin them to the upper left of the document. Here’s a demo of that issue:
Animating/Transitioning clip-path
When you declare a basic shape as a clip-path, you can animate it! Dirk Schulze has a wonderful article covering this same kind of stuff which features this demo:
See the Pen Wonderful clip-path animation by Chris Coyier (@chriscoyier) on CodePen.
Here’s a simple example of code:
div {
transition: 0.4s cubic-bezier(1, -1, 0, 2);
clip-path: polygon(50% 5%, 0% 100%, 100% 100%);
}
div:hover {
clip-path: polygon(50% 19%, 0 76%, 100% 76%);
}
Try it out:
See the Pen Transitioning clip-path by Chris Coyier (@chriscoyier) on CodePen.
Masking
There was a WebKit-only version of masking where you could link up a raster image or define a gradient to be a mask. It looked like this:
img {
width: 150px;
-webkit-mask-image: -webkit-gradient(
linear, left top, right bottom,
color-stop(0.00, rgba(0,0,0,1)),
color-stop(0.35, rgba(0,0,0,1)),
color-stop(0.50, rgba(0,0,0,0)),
color-stop(0.65, rgba(0,0,0,0)),
color-stop(1.00, rgba(0,0,0,0)));
}
As far as I know, that’s deprecated. That’s the definitely deprecated gradient syntax, and when I changed it to the new syntax, it didn’t work. So yeah, probably deprecated. It still works though:
See the Pen Old School WebKit Masking by Chris Coyier (@chriscoyier) on CodePen.
And it gave birth to things like this old tutorial WebKit Image Wipes, which still works (you know, in Blink/WebKit land).
More modern references I’ve found only mention masks as being defined in SVG and referenced in CSS by ID or URL. Here’s an example two ways. The mask is defined in the SVG, and on the left, the image is within the SVG in an
See the Pen 41d6e36ac584ee0401064d1cdb88fc67 by Chris Coyier (@chriscoyier) on CodePen.
Check out this demo in Firefox too (example code lifted from Dirk Shulze’s article). This kind of thing is a little dangerous at the moment, as it doesn’t just not work in WebKit/Blink, it erases the element it’s applied to entirely.
You can also link up an entire SVG file as the mask, like:
.mask {
mask: url(mask.svg);
}
Mask Types
.mask {
mask-type: luminance; /* white = transparent, grays = semi-transparent, black = opaque */
mask-type: alpha; /* transparent areas of the image let image through, otherwise not */
}
Border Masks
This is very similar to how border-image works in CSS. You define an SVG image, and nine-slice scaling is applied (like a tic-tac-toe board over the image). The corners are used in the corners, the edges (can be) repeated along the edges, and the middle (can) stretch in the middle. The basics of that:
.border-mask {
/* Note that the properties aren't quite the same */
-webkit-mask-box-image: url(stampTiles.svg) 30 repeat;
mask-border: url(stampTiles.svg) 30 repeat;
/* Image, again, from http://www.html5rocks.com/en/tutorials/masking/adobe/ */
}
Here’s a demo of regular masking versus border masking:
See the Pen Masking in CSS with SVG Image by Chris Coyier (@chriscoyier) on CodePen.
Browser Support
It’s so hard so summarize succinctly, since different properties and even values have different support levels all over the place. Not to mention how you use them and on what. It’s a wilderness out there, so I’d recommend using as progressive enhancement the best you can at the moment. Which might be a little tough, as there isn’t even Modernizr tests for this stuff really.
As far as prefixing goes: use the non-prefixed and -webkit-
prefix on pretty much everything.
Yoksel has a great browser support chart she made related to all of this:
See the Pen CSS and SVG Masks by yoksel (@yoksel) on CodePen.
More
- clip-path here on the CSS-Tricks Almanac
- Clipping and Masking in CSS
- clip-path on WPD
- clip-path on MDN
- Clipping and masking on MDN
- The (deprecated) CSS Clip Property (Impressive Webs)
- Spec on CSS Masking
- CSS Masking by Dirk Schulze
- Clipping in CSS and SVG – The clip-path Property and
<clipPath>
Element by Sara Soueidan - Pens tagged clip-path on CodePen
- Demos and browser support demo Pen by Yoksel
- SVG Masks by Jakob Jenkov
- Alan Greenblatt’s research on browser support levels for clipping and masking features
- Video of a talk: Razvan Caliman – Cutting-edge CSS Features for Graphics: CSS Masking, Blending and Shapes
I believe
clip-path: inset()
works the same asclip: rect()
.clip-path: inset()
works in browsers that currently support clip path. I haven’t heard anything new withrect()
,rectangle
,inset-rectangle
, or whatever else.Yes thank you! Ana Tudor pointed that out to me right after publishing as well. That’s why I’m so lucky getting to write articles like this, because I learn just as much from the feedback as I do from the research.
See my original comment below – I should probably have replied here originally.
The old
clip: rect()
and the newclip-path: inset()
are quite different in how they interpret the 4 arguments:rect()
does so from top/left for all arguments,inset()
from each of the 4 sides.“I’m probably just missing something, but I’m not finding an obvious way to get the path to move along with the element it’s applied to.”
It works in Firefox (34 here). So either Firefox makes it works while it shouldn’t, or the WebKit/Blink implementation is buggy with respect to this.
Firefox also places the SVG mask’s two circles farther to the right and left, which looks like the result you’d expect from the SVG code (I might be wrong).
Works for me too, FF 34.0.5 beta.
Something is funky there for sure. Even if I did something incorrectly (probably), the behavior cross-browser should be the same. So somebody has a bug, it’s just not clear who.
Great article…Article does not explicitly say it but you can use PNG images as masks as well…
Clip and masking are those things, like flexbox, that I’d like to use in today ideas with being completely sure that it will be crossbrowser. Good thing is that, major browsers are getting faster in implementing new things like this, so I’m very optimistic in looking for more of new progresses.
Glad to see that you are well informed Chris, and spread these bunch of tricks to others. Thanks!
This is the default behaviour of SVG clip-paths: the contained graphics are defined in the user coordinate system. However, you can control that by setting the
clipPathUnits
property on the<clipPath>
element. Default isuserSpaceOnUse
, the other option isobjectBoundingBox
. Note that if you useobjectBoundingBox
, not only does the origin change, but so does the definition of a unit — a length of 1 is treated as a length of 100%.So, how does this apply to non-SVG content? How are the user coordinate system and object bounding box defined? Unfortunately, the specs are a little confusing (which probably explains the cross-browser problems).
The clipping and masking specs link to the following definition of user coordinates in the CSS transforms specs
Since the default value for
transform-box
isborder-box
, it looks like Firefox has it right and the user coordinate system for a clipped element should move with the element itself.In the meantime, you can get cross-browser results using
objectBoundingBox
units:However, there’s a complication. The CSS masking spec uses the following definition:
That last sentence is inconsistent with how objectBoundingBox works in SVG and with how browsers currently implement it (FF and Chrome tested). I’ll follow-up on the W3C mailing list — it might be an error, or there might be a non-standard definition of “user coordinates” somewhere that I’m missing (in SVG, “user coordinates” means “coordinates specified without units”, which work out as width/height when dealing with bounding box coordinate systems).
In the meantime, if you want to be extra sure, you could use percentages instead of decimals within your objectBoundingBox clipPath.
One other thing to mention about masking vs clipping is that masking is more memory and computation intensive since it’s a full image, and everything has to be processed pixel-by-pixel. So only use masks when you actually want the partial transparency effect; for crisp edges, use clip paths.
Of course, given the disappoint results of the compatibility discussion, it doesn’t look like masks will be practical for non-SVG content for a little while yet, so that’s another reason to use clip paths!
This is a minefield, no way around that. It looks like it could be awesome, and open up a whole new world of tricks, if it ever gets finalised properly. Be useful in 10 years knowing the web ;)
You can move it with transforms. (tested in chrome) eg:
Fantastic article! I’m glad that css3 got so far, also I like this svg-css love affair that is going on lately. About the bug that the clip-path is fixed, did you try putting the animation on the parent of the image?
Those path animations are stunning. Thanks for sharing.
Extremely Cool and useful!
Thanks for posting.
its works, thanks really cool
I don’t think this has been mentioned in the above techniques, but I achieved a similar effect by keeping both mask and image actually within the SVG. Obviously, doesn’t give you nice flexibility in CSS, but if you have a way of generating this SVG code from e.g CMS, it’s pretty manageable
See the Pen SVG image in mask by tobystokes (@tobystokes) on CodePen.
“Looks like this is is what rect() used to be like with clip”
Besides this being very useful information for a thing I’m working on at the moment, I really enjoy the liberal use of Harry Potter in this article :D
Awesome! I was just wondering about this topic a few weeks ago because I wanted to animate a futuristic sliding UI without After Effects.
Hey Chris, great article!
I think the definition of the old
clip: rect()
in the article is a bit off though, as well as the comparison with the newerclip-path: inset()
.The
clip: rect()
thing takes 4 arguments for the sides to be clipped, but they mark the distance from the top/left edges rather than from all 4 edges.The first argument is the top clip distance from the top border edge, the second argument is the right clip distance from the left edge, the third is the bottom clip distance from the top edge and the fourth is the left clip distance from the left.
(Sidenote: This is why the old
clip: rect(0 0 0 0)
technique is used for accessible hiding techniques such as the one in Snook’s example – it makes it easy to crop the whole element away without knowing dimensions.)The
inset()
notation forclip-path
does however work from the 4 border edges, so it gives a completely different (and arguably more flexible) result.I put up a simple JSBin example illustrating the differences.
Hey Chris (and friends),
First off – thanks so much for your awesome website. It’s a fantastic resource for all of us so cheers to you for constantly keeping it up to date with fresh and relevant articles. Basically, you’re awesome.
I was wondering what the best route is to go about masking (or clipping) a div (or any other element for that matter). The examples you’ve suggested above seem to relate mostly to images, which is great and very useful, but I was wondering if, for example, I have a html5 video or even youtube embed that I want to have a clipping path or mask, what would be the best route? I’ve heard canvas mentioned before, but is there any way to do it via css?
Cheers people.