Clipping and Masking in CSS

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

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.

The Old/Deprecated clip

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 positioned
  • clip can only do rectangles.

Pretty limiting. So let’s move on.

The New clip-path

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:

Left: clip path moves along with image in Google Chrome 54 (good). Right: clip path is pinned to the upper left and doesn’t move along with image in Safari 10 (bad).

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 tag. On the right, that mask applied to an in the HTML (which only seems to work in Firefox at the moment).

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