CSS Masking

HTML5 Rocks

Two commonly used operations in computer graphics are clipping and masking. Both operations hide visual portions of an element. If you have worked with SVG or HTML Canvas before, these operations are probably not new for you. Clipping defines the region of an element that is visible. Everything around this region does not get rendered - it gets "clipped". On masking, a mask image is composited with the element, affecting the alpha channel of this element. Portions of a masked element get fully or partially transparent. The new CSS Masking specification aims to bring these two operations to the HTML world.

Clipping in CSS 2.1

CSS 2.1 already specified the clip property. This property is limited to rectangular clipping with the rect() function taking four distance arguments for the top, right, bottom and left edges. The annoying part: The clip property applies to absolutely positioned elements exclusively. The property is just ignored on other elements.

CSS:

img {
  position: absolute;
  clip: rect(10px, 290px, 190px, 10px);
}

HTML:

<img src="image.jpg" width="568">

original unclipped image
'clip' property applied to an image

The clip property is limited to specific elements in SVG as well. This is one reason why the SVG specification added the clip-path property that is adapted by CSS Masking now.

The clip-path property

The clip-path property can be applied to all HTML elements, SVG graphic elements and SVG container elements. It either references a <clipPath> element or one of the basic shapes introduced with CSS Exclusions.

The <clipPath> element takes any graphical element from SVG and uses them as clipping region. Graphical elements in SVG are <rect>, <circle>, <ellipse>, <path>, <polygon>, <image> and <text>. <clipPath> allows combining multiple graphical elements as well. The union of all shapes is then used as clipping region. The following example demonstrates the use of <clipPath>:

CSS:

img {
  clip-path: url(#clipping);
}

HTML:

<svg>
  <defs>
    <clipPath id="clipping">
      <circle cx="284" cy="213" r="213" />
    </clipPath>
  </defs>
</svg>

<img src="image.jpg" width="568">

Basic shapes on the other hand do not require any SVG markup. They were added to clip-path to provide easy shorthand functions for simple clipping operations.

  • inset(<top> <right> <bottom> <left> [ round <border-radius> ]?) defines a rectangle, similar to the rect() function of clip. The paramters describe the offsets from the top, right, bottom and left side. The function also has optional radius parameters with the syntax of the border-radius property for rounded rects.
  • circle(<r>? [ at <position> ]?) defines a simple circle with an optional radius parameter. In addition, the optional position parameter described the center point of the circle. <position> has the same syntax as the background-position property.
  • ellipse(<rx> <ry>? [ at <position> ]?) defines an ellipse with a horizontal and an optional vertical radius as well as a center point based on the background-position property's syntax.
  • polygon(<x1> <y1>, <x2> <y2>, ..., <xn> <yn>) defines a polygon based on the point list parameters.

Keywords like content-box, border-box, margin-box can be used in combination with basic shapes to position and size the specified clipping path. Defined without basic shapes, keywords act as a clipping path themselfes taking the border-radius property into account.

The CSS markup can look like the following example:

img {
  clip-path: polygon(0px 208px, 146.5px 207px, 147px 141.2px, ...);
}

Clipping can be very useful for the presentation of visual content. The following examples apply different clipping operations to images.

But clipping can be useful for UI design as well. In the following example a chevron indicates a continuing list.

Scroll the list to see the effect.

Note that clip-path (as well as clip) clips out any aspect of an element including backgrounds, borders and any scrolling mechanism.

Animation of clip-path

Both, basic shapes and the content of a <mask> element can be animated. The following example has a star shape animation:

Animation of clipping

Here is the source code for the animation of the basic shape:

img:hover {
  clip-path: polygon(0px 208px, 146.5px 207px, 147px 141.2px, ...);
  animate: star 3s;
}

@keyframes star {
  0% {
    clip-path: polygon(0px 208px, 146.5px 207px, 147px 141.2px, ...);
  },
  100% {
    clip-path: polygon(0px 208px, 146.5px 207px, 147px 141.2px, ...);
  }
}

Masking

The second operation after clipping is masking. A mask image is used as some kind of "color mesh", to filter the visual portions of an element. In the following paragraphs I'll explain the difference between two kind of masks: the luminance mask and the alpha mask.

Luminance Mask

For luminance masks, the mask image is transformed to a rasterized gray scale image first (if it isn't in gray scale already). The "lighter" a portion of the mask image is, the more of the masked element will be visible on the same position. For instance black indicates fully transparent, white indicates fully opaque and a gray tone indicates partial transparency of the element.

Alpha Mask

Alpha masking uses the same principle as luminance masking. The difference to luminance masking: just the alpha channel of the image matters. The less opaque a portion of the mask image is, the less visible the element will be at the same position.

To summarize: both masking types affect the transparency level of the element. The image below is the result of both masking operations above.

The CSS Masking specification defines two shorthand properties for masking: mask and mask-border.

The mask property

The mask property combines image masking with referencing a mask as follows.

The first way is the use of the mask-image, mask-repeat, mask-position, mask-clip, mask-origin and mask-size properties that are specified similar to their counter part background with background-image. Like for background-image, it is possible to define multiple mask sources. Each mask source is an image defined by CSS3 Images. All mask sources will be combined to form a single mask image. This mask image is used to mask the element and its content as described above. Possible image values are any rasterized image format like JPG or PNG, SVG graphics, or predefined image values like CSS gradients.

The first masking example above can simply be realized with the following code:

img {
  mask-image: url(mask.svg);
}

If the mask source should be stretched to the size of the content, just use the shorthand property mask, as you would do it for the background with the shorthand property background:

img {
  mask: url(mask.svg) top left / cover;
}

The second way references a <mask> element as defined by SVG 1.1. A <mask> element takes any graphical element as well as grouping elements from SVG and uses them to create the mask image.

CSS:

img {
  mask: url(#masking);
}

HTML:

<svg>
  <defs>
    <linearGradient id="gradient" x1="0" y1="00%" x2 ="0" y2="100%">
      <stop stop-color="black" offset="0"/>
      <stop stop-color="white" offset="1"/>
    </linearGradient>

    <mask id="masking" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
      <rect y="0.3" width="1" height=".7" fill="url(#gradient)" />
      <circle cx=".5" cy=".5" r=".35" fill="white" />
    </mask>
  </defs>
</svg>

<img src="image.jpg" width="568">

Which would look like the following image:

The mask-border property

The mask-border property allows to divide a mask image into 9 tiles: four corners, four edges and the middle piece. These pieces may be sliced, scaled and stretched in various ways to fit the size of the mask image area. The property borrows the functionality from border-image and allows powerful masking at the edges and corners of the content. The following example demonstrates the behavior of the property:

img {
  -webkit-mask-box-image: url("stamp.svg") 35 repeat;
  mask-border: url("stamp.svg") 35 repeat;
}

The following image is used as mask image and divided into 9 parts:

Pattern of a stamp to be used as mask

These parts are now used to mask the corner and edges of the content and result in the following view:

Image masked with mask-border

Browser support

The interesting part is the support in browsers. When it comes to feature detection it gets kind of hairy.

All browsers support clip as specified. All browsers support the mask and the clip-path properties as specified in SVG 1.1 on SVG elements. But just one browser lets you apply the properties on HTML elements: Firefox (sort of). Lets get into more details.

The clip-path and the mask property with references to <clipPath> and <mask> elements work in Firefox out of the box; both properties are unprefixed. On the other hand, mask-image, mask-border and related properties are not supported at all. Basic shapes for clipping aren't supported either.

Blink and WebKit based browsers, like Chrome and Safari, have support for mask-image, mask-border (currently called -webkit-mask-box-image in both implementations) and related properties. All of them are prefixed and can just be applied to HTML elements. The nightly builds of both browsers already support the prefixed -webkit-clip-path property with basic shapes and referencing <clipPath> elements on HTML. WebKit supports the additional box-sizing keywords.

If you want to try clipping and masking, make sure that you use the prefixed and unprefixed properties. The unprefixed properties must use reference of <mask> or <clipPath> at the moment.

<style>
  #image {
    mask: url(#mask);
    -webkit-mask: url(mask.svg) top left / cover;
  }
</style>

<img id="image" src="coolImage.jpg" width="400">

<svg width="0" height="0">
  <mask id="mask">
    ...
  </mask>
</svg>

But be careful, other browsers don't have further masking or clipping functionalities for HTML elements yet.

Acknowledgment

Thank you very much to Arno Gourdol for providing the picture from the Humayun's Tomb.

Comments

0