Visual Effects in CSS: Clipping and Masking

Clipping and masking are ways of concealing or revealing portions of a layer or document.

Although they’re defined by the same specification, clipping and masking work slightly differently.

  • Clipping is a bit like using a cookie cutter. It uses a closed vector path, shape, or polygon to trim areas of a layer. Parts of the layer that lie outside the vector path are hidden. Parts that are inside the path are visible.
  • Masking works more like peeled away masking tape, where painted or filled areas of the masking image expose the layers underneath.

In practical terms, the big difference is that clipping uses the clip-path property, and masking uses the mask shorthand or the mask-* longhand properties. Masking is more complicated to use, but it’s also a little more flexible.

In some cases, you may want to wrap your clipping and masking rule sets in an @supports block.

1. The clip-path Property

The clip-path property accepts a basic shape or the URL of an SVG clip path. In the

“Shapes” section of Chapter 5, “Layouts”, we saw that there are five basic shape functions

17

defined by the CSS Shapes specification:

  • inset() (used to create rectangles)
  • circ1e()
  • eLLipse()
  • poLygon()
  • path() (which must be an SVG path data string)

Let’s look at an example using the ellipse() shape function. Our markup is simple-just an <img> element:

<img src=”milk-and-strawberry-in-a-spoon.]pg”>

And here’s our CSS:

img {

display: block;

height: 95vh;

margin: 2rem auto;

width: auto;

/* Creates an oval shape that effectively crops the image */

clip-path: ellipse( 50% 50% at 50% 50% );

}

Areas of the photograph that fall outside the clipping shape aren’t painted to the screen, as seen in the image below.

2. Using clip-path with Polygons

The clip-path property also accepts a polygon shape as a value. Polygons are closed shapes made from straight lines. The poLygon() function accepts a comma-separated list of coordinates for each point that makes up the polygon. Let’s change our CSS to use a hexagonal polygon:

img {

display: block;

height: 95vh;

margin: 2rem auto;

width: auto;

/* Creates a hexagon clipping area */

clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);

}

The following image shows the result of using a hexagonal polygon as a clip path.

Polygons can be tricky to create. Bennett Feely’s Clippy is perhaps the best web-based tool for creating polygon clip paths. Firefox’s developer tools also include a rudimentary polygon shape editor.

3. Creating More Complex Clipping Regions with path()

For more complex shapes made of curves and arcs alone or in combination, we need to use the path() function. path() , unlike poLygon() , accepts an SVG data path (the value of the d attribute of a path element) as its argument:

img {

display: block; height: 95vh; margin: 2rem auto; width: auto;

/* Creates a blob-shaped clipping area */

clip-path: path(‘m 104.3412,-94.552373 a 235.57481,242.55224 0 0 0 -235.57475,242.551213

235.57481,242.55224 0 0 0 235.57475,242.55309 235.57481,242.55224 0 0 0 35.90179,-2.83462

116.28209,116.28209       0 0 0  -0.3821,9.42037 A 116.28209,116.28209 0        0 0  256.14311,513.4198

116.28209,116.28209       0 0 0  363.17355,442.59305      288.39157,288.39157  0 0  0   645.41323,671.74187

288.39157,288.39157       0 0 0  933.80515,383.34996      288.39157,288.39157  0 0  0   645.41323,94.957809

288.39157,288.39157       0 0 0  587.24283,100.88816      116.28209,116.28209  0 0  0   587.69599,90.618827

116.28209,116.28209 0 0 0 471.41379,-25.663306 116.28209,116.28209 0 0 0 355.88656, W7.404732 230.96342,158.23655 0 0 0 328.66979,73.946711 235.57481,242.55224 0 0 0 104.34119 ,-94.552373 Z’);

}

The result is pictured below.

The easiest way to create path data is by creating an SVG image that contains a path element and grabbing value of the path element’s d attribute. Using the path() function lets us create more complex shapes that include curves and arcs, in addition to straight lines.

Notice here that the points of the path exceed the bounds of the element being clipped. Parts of the image we’ve used to clip the photograph extend outside of the <img> element’s bounds. Because path() uses coordinates and pixels, there isn’t a way to scale it proportionally to fit an element’s dimensions as can be done with other basic shapes.

This also means that path() isn’t responsive. It won’t change with the size of the viewport or the container. Basic shapes, however, can be responsive if you use percentages for the coordinates of each vertex.

Clipping paths don’t affect the geometry of an element; they only affect its rendering. DOM functions such as getcLientRects() return the dimensions of the entire element, not the clipped region.

Clipping paths do, however, affect the interactive area of an element. If, for example, you apply a clip-path to an <a> element, only the area within the bounds of the clip path will receive pointer and mouse events.

4. Using clip-path with URLs

Most current browser versions also support using SVG URLs as values. However Chromium- based browsers and Safari require the SVG to be inline. To date, Firefox is the only browser that also supports external SVG images. Of course, this may have changed between the writing of this paragraph and your reading of it.

However, we can’t use just any SVG image. The image needs to contain a cLipPath element, which should have one or more shapes, paths, or lines as children. An SVG cLipPath must also have an id attribute, which is how we’ll reference it. Here’s an example of using an SVG cLipPath element:

<svg xmlns=”http://www.w3.org/2000/svg” viewBox=”0 0 400 400″>

<clipPath id=”star”>

<path

transform=”translate(200, 0) scale(1.8)”

style=”fill: #000;”

d=”M 328.05692,385.18211 203.81502,322.90671 82.079398,389.94936 102.91396,252.54413 

1.5342416,157.48399 138.65261,134.83828 197.73212,9.0452444 261.64137,132.45466 l

137.89287,17.31576 -97.62028,98.91692 z” />

</clipPath>

</svg>

To use this image as a clip path, we’ll need to use the urL() function:

img {

display: block;

height: 95vh;

margin: 2rem auto;

width: auto;

/* Creates a star-shaped clipping area */

clip-path: url(#star);

}

You can see the result in the image below.

External SVG images work similarly. We just need to add the external URL of the image, such as urL(https://exampLe.com/star.svg#svg) .

Using an inline SVG image will affect the layout of other items on your page, even though it’s rendered invisibly. Luckily, there’s an easy workaround: set the dimensions and position of the SVG element:

svg.clipper {

position: absolute;

top: 0px;

left: 0px;

width: 0px;

height: 0px;

}

Since the contents of a clip path are only rendered when the clip path gets applied, it’s still available to the document. However, the CSS above prevents the root <svg> element from taking up space.

Using display: none won’t work. It prevents a box from being generated altogether. Similarly, visibility: hidden not only hides the root SVG element and its children, but also the layers you want to clip. Positioning the clipping path offscreen, or setting its height and width to zero avoids those issues.

Clipping defines a contiguous region of a layer that will be painted to the screen. Masked areas, on the other hand, don’t need to be contiguous. Unlike clip-path , they can be also be resized and positioned.

5. Masking

Masking is likely familiar to you if you’ve ever worked with graphics editors such as Photoshop, Sketch, or Glimpse. It’s an effect created when the painted areas of one layer reveal portions of another layer, as illustrated below.

Masks can be defined using the mask shorthand property, or the mask-* longhand properties. The behavior of masking properties mimics those of background properties. Masking, however, also affects foreground layers, and creates a new stacking context for those layers.

At the time of writing, Firefox has the most complete support for CSS masking. Firefox supports all masking properties, without a prefix. Chromium- and WebKit-based browsers support prefixed versions of all masking properties, with the exception of mask-mode . Firefox also includes support for the -webkit- prefixed subset of properties. The table below details mask property support across the three browser engine families.

The CSS Masking Module Level 1 specification also defines a set of mask-border-* properties. As of this writing, however, support for these properties is still in the experimental phase. Safari, Chrome, and Edge use non-standard -webkit-mask-box-* properties. Firefox lacks any support. Since support and standardization are still in the works, we won’t cover them here.

6. Creating a Mask with mask-image

To create a mask, use mask-image . Its value should be either a CSS image created with a gradient function, or the URL of an SVG <mask> element.

In SVG, the <mask> element is a container for other shapes that form the contours of the mask. It’s similar to cLipPath . The following code shows a simple example of a star-shaped mask:

<svg xmlns=”http://www.w3.org/2000/svg” viewBox=”0 0 400 400″>

<mask id=”star”>

<path style=”fill: #000;” d=”M 328.05692,385.18211 203.81502,322.90671 82.079398,389.94936

102.91396,252.54413 1.5342416,157.48399 138.65261,134.83828 197.73212,9.0452444 261.64137,

132.45466 l 137.89287,17.31576 -97.62028,98.91692 z” />

</mask>

</svg>

We can then reference this mask like so:

img {

height: 95vh;

margin: auto;

display: block;

width: auto;

mask-image: url(‘masks.svg#star’);

}

At least, that’s how it’s supposed to work, according to the specification. In practice, referring to an SVG <mask> element only works in Firefox. You can, however, use an SVG or PNG image as a masking image. Gradients created using the Linear-gradient() function also work, but radial and conic gradients don’t (as of this writing).

For our first masking example, we’ll use the star.png shown below. It has a filled, star-shaped area where alpha=1, and it’s surrounded by transparent pixels where alpha=0.

Here’s our CSS:

img {

height: 95vh;

margin: auto;

display: block;

width: auto;

-webkit-mask-image: url(‘star.png’); /* Chromium and WebKit browsers */

mask-image: url(‘star.png’);

}

As you can see in the image below, the painted or filled areas of star.png reveal the photograph below it. Transparent areas of star.png hide the layer (or layers) below it.

In this example, our mask image includes some areas that are 100% opaque and some that are 100% transparent. However, the rendering of masked layers may be affected by the alpha transparency or color of the masking layer.

7. Managing Mask Processing with mask-mode

Take a look at the image below, where a linear gradient transitions from an opaque to a transparent color. Notice how the image fades into the background, particularly toward the bottom.

This effect is created by using a linear gradient as the masking image:

img {

mask-image: linear-gradient( to bottom, #000, #fff0 90% );

}

Our gradient transitions from opaque black, where alpha = 1, to white with an alpha transparency of 0. Where the mask image is 100% opaque, the toucan photograph is also fully opaque. As the gradient transitions from opaque to transparent, the image fades in opacity, approaching 0% at the bottom of the image.

This is an alpha mask. With alpha masks, the browser uses the alpha channel—and only the alpha channel—of the masking image to determine the pixel colors of the final painted layer. Portions of a layer that sit beneath the transparent portions of a mask image are invisible.

CSS (and SVG) also supports luminance masks. Luminance masks use the values of each color channel (red, green, and blue), in addition to the alpha channel, to calculate the color of the pixels painted to the screen. With luminance masks, areas of a layer that are overlapped by black or transparent colors will be invisible. Other areas of the layer will be more or less opaque based on the luminance of the colors in the masking layer.

We can change the behavior of a masking layer using the mask-mode property. It accepts one of three values: alpha , Luminance , or match-source . Its initial value is match-source .

When the mask image is an SVG <mask> element and the value of mask-mode is match- source , the browser uses the computed value of the <mask> element’s mask-type property . However, when the mask is an image, such as a gradient or PNG, the mask is processed as an alpha mask.

Let’s look at an example using the linear gradient and photograph shown below, where a striped gradient is used to illustrate the difference between alpha and luminance masking.

Firstly, let’s set up our linear gradient mask image. Although the CSS below doesn’t include prefixed properties, don’t forget to include the -webkit- prefix (such as -webkit-mask-image ) when using this in the real world:

body {

background: #000;

}

img {

height: 95vh;

margin: auto;

display: block;

width: auto;

mask-image: linear-gradient(#f006 0 33.33%, #ff0a 33.33% 66.66%, #080 66.66% 100%);

mask-mode: alpha;

}

Our gradient consists of a transparent red stripe ( #f006 ), followed by a band of transparent yellow ( #ff00a ), and a bright, opaque green. We’ve also set a background color of black for our document. The image below shows the result of using a linear gradient with transparent colors as a mask image.

In this example, mask-mode is alpha , so each band of the gradient mask only changes the opacity of the photograph. If we change the value of mask-mode to Luminance , however, the bright yellow band of the gradient is much brighter than the red band at top, and the green band is slightly darker than the yellow band. In fact, the red band is almost as black and opaque as the document’s background color.

Luminance refers to the perceived lightness of a color. Yellow has a higher luminance than green. This particular green, at full opacity, has a higher luminance than our semi-transparent red. As a result, the yellow band is the brightest of the three.

Opaque white, of course, has the highest luminance value of all. Changing opaque blue to white creates that bright, full-color band across the bottom of the photo, as shown in the image below. Opaque white results in opaque pixels when the value of mask-mode is Luminance .

8. Making Mask Images Repeat (or Not) with mask-repeat

The behavior of mask-image is a bit like that of background-image . For example, mask images repeat horizontally and vertically to fill the dimensions of the element to which they’re applied.

We can prevent a mask image from repeating with the mask-repeat property. Let’s update our mask-image CSS from earlier:

img {

height: 95vh; margin: auto;

display: block; width: auto;

-webkit-mask-image: url(‘star.png’);

mask-image: url(‘star.png’);

-webkit-mask-repeat: no-repeat;

mask-repeat: no-repeat;

}

This gives us the result shown below.

As you may have figured out, mask-repeat behaves a bit like background-repeat . In fact, it accepts the same values (up to two):

  • repeat-x : repeats the mask image horizontally
  • repeat-y : repeats the mask image vertically
  • repeat : repeats the mask image in both dimensions
  • space : repeats the mask as often as it can be repeated without being clipped, then spaces them out
  • round : repeats the whole mask image, scaling the image up or down if necessary
  • no-repeat : prevents the mask image from repeating

When repeat-x and repeat-y are used as mask-image values, it’s the same as using mask- image: repeat no-repeat and mask-image: no-repeat repeat ,respectively.

9. Resizing Mask Images with mask-size

Mask images, similarly to background images, can be resized using the mask-size property. Let’s change our CSS a little bit. In the code below, we’ve resized the mask image and changed how it repeats:

img {

height: 95vh;

margin: auto;

display: block;

width: auto;

-webkit-mask-image: url(‘star.png’);

mask-image: url(‘star.png’);

-webkit-mask-repeat: round;

mask-repeat: round;

-webkit-mask-size: 5vw auto;

mask-size: 5vw auto;

}

Setting the mask-size property means that it can repeat more often across each dimension, as shown below.

As with background-size , permitted values for mask-size include length and percentages, as well as the cover and contain keywords.

It’s also possible to change the mask-position and mask-origin of a masking image. These properties share values and behavior with their background-position and background-origin counterparts.

10. Using Multiple Mask Images

As mentioned earlier in this section, mask-image supports multiple masks, like its background-image counterpart. We’ll switch from PNG images to SVG images for the examples in this section. We’ll use circLe.svg and square.svg , shown below.

The gray areas of the images above are just to show the position of the circle and square within the bounds of the SVG document. Those areas are transparent.

Let’s change our CSS to use multiple mask images:

img {

height: 95vh;

margin: auto;

display: block;

width: auto;

-webkit-mask-repeat: no-repeat;

mask-repeat: no-repeat;

-webkit-mask-image: url(‘circle.svg’), url(‘square.svg’);

mask-image: url(‘circle.svg’), url(‘square.svg’);

}

Multiple masking images follow the same ordering as background-image . The first image in the list becomes the topmost layer mask.

Here, the circle and square images overlap to form a single shape, as shown above. We can shape how masking layers are visually combined using the mask-composite property.

11. Managing Mask Layer Compositing with mask-composite

Compositing is the process of combining separate image layers or sources into a single visual layer. With mask images, the initial or default behavior is to add the layers together: mask- composite: add . The mask-composite property accepts one of four values: add , subtract , intersect , or exclude . The table below shows the effect of each property when the order of mask-image is urL(‘circLe.svg’), urL(‘square.svg’) .

The order of the layers in mask-image matters in some cases. Changing the order of our mask images to mask-image: urL(‘square.svg’), urL(‘circLe.svg’); , for example, changes the effect of mask-composite: subtract. The image below shows this difference. Since square.svg is now the top layer, circle.svg gets subtracted from it.

12. Using the mask Shorthand Property

You may find it easier to use the mask shorthand property. Let’s rewrite our mask-composite example using the mask shorthand:

img {

mask: no-repeat url(‘circle.svg’), no-repeat url(‘square.svg’);

}

Notice that we’ve repeated the mask-repeat value for each image in our mask list. If we wanted to get fancy and change the size and position of square.svg , we could use the following:

img {

mask: no-repeat url(‘circle.svg’), no-repeat url(‘square.svg’) 0 0 / 200px 200px;

}

The CSS above moves our square to the top-left corner of the container, and resizes it to 200px by 200px , as pictured below.

Longhand properties are more verbose, but clearer. Shorthand properties save bytes, but potentially at the expense of readability.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *