← Back to Index

Lesson 6

Clipping & Masking

Painting shapes with gradients and patterns is covered. Now: how to hide parts of them. Clipping and masking are two complementary techniques for controlling which portions of an element are visible - and they both use the same <defs> + url(#id) referencing pattern covered in Lesson 5.

1. The Concept

Both clipping and masking control the visibility of elements - they hide parts of a shape or group. The difference lies in how they determine what's visible:

But SVG is vectors, not pixels?

Correct - clipping is defined as a geometric containment test (is this point inside the clip shape?), not a pixel operation. It's pure maths at the definition level. But when the browser renders the SVG to screen pixels, pixels on the clip boundary get anti-aliased (partial opacity based on sub-pixel coverage) so edges look smooth rather than jagged. This anti-aliasing is a rendering artifact, not a feature of the clipping model - the model itself is strictly inside/outside. Masks, by contrast, can produce gradual transparency anywhere, not just at the boundary.

Both are applied using the familiar pattern:

Clipping = cookie cutter. Masking = spray paint stencil.

A clip path is a cookie cutter - hard edges, on/off, the shape's outline is all that matters. A mask is a spray paint stencil - soft edges, gradual fade, the brightness or alpha of every pixel controls how much shows through.

2. clipPath

A <clipPath> element contains one or more shapes that define the visible region. Anything inside the clipping shape's outline is visible; everything outside is hidden. The geometry is all that matters - fill colour, stroke, opacity on the shapes inside the <clipPath> are completely ignored.

Basic structure

<svg viewBox="0 0 200 200">
  <defs>
    <clipPath id="circle-clip">
      <circle cx="100" cy="100" r="80"/>
    </clipPath>
  </defs>

  <!-- This rect is clipped to a circle -->
  <rect width="200" height="200" fill="coral"
        clip-path="url(#circle-clip)"/>
</svg>

Result: the coral rect is clipped to the circle - only the circular area is visible.

Key points about clipPath

clipPathUnits

Demo: Clipping a rect to a circle

Original rect (faded) Clipped to circle

Left: the original gradient rect. Right: the same rect clipped to a circle - only the circular region is visible.

Demo: Text as a clip path

Since <text> is a valid shape in SVG, you can use text outlines as your clipping region - creating a text-shaped window into anything:

SVG

Text as a clip path - the letters "SVG" become a window into a gradient + dot pattern below.

<defs>
  <clipPath id="text-clip">
    <text x="250" y="110" font-size="80" font-weight="900"
          text-anchor="middle">SVG</text>
  </clipPath>
</defs>

<!-- Anything inside this group is clipped to the text outline -->
<g clip-path="url(#text-clip)">
  <rect width="500" height="160" fill="url(#my-gradient)"/>
  <rect width="500" height="160" fill="url(#my-pattern)" opacity="0.6"/>
</g>

3. mask

Where <clipPath> is binary, <mask> uses the luminance (brightness) or alpha channel of its content to determine visibility on a per-pixel basis. This enables soft edges, gradient fades, and feathered borders that clipPath simply can't achieve.

How luminance masking works

The mask's content is rendered to an offscreen buffer, converted to luminance values, and those values become the opacity of the masked element at each pixel.

Key attributes

Default mask region: extends 10% beyond the element

By default, the mask area extends from -10% to 110% of the element's bounding box in both axes (x="-10%" y="-10%" width="120%" height="120%"). This gives you a small buffer so mask edges don't abruptly clip right at the element boundary. You can override these values if you need a different coverage area.

Demo: Gradient fade mask

A gradient from white to black inside the mask creates a smooth fade-out effect:

A horizontal white-to-black gradient in the mask causes the colourful rect to fade from fully visible (left) to fully hidden (right).

<defs>
  <!-- A gradient from white (visible) to black (hidden) -->
  <linearGradient id="fade-grad">
    <stop offset="0%" stop-color="white"/>
    <stop offset="100%" stop-color="black"/>
  </linearGradient>

  <mask id="fade-mask">
    <rect width="100%" height="100%" fill="url(#fade-grad)"/>
  </mask>
</defs>

<rect width="400" height="150" fill="coral" mask="url(#fade-mask)"/>

Demo: Soft-edged shape mask

A radial gradient from white (centre) to black (edges) creates a spotlight or vignette effect:

A radial gradient mask creates soft, feathered edges - the checkerboard pattern fades smoothly into nothing at the edges.

<defs>
  <!-- Radial gradient: white centre (visible) to black edges (hidden) -->
  <radialGradient id="soft-grad" cx="0.5" cy="0.5" r="0.5">
    <stop offset="0%" stop-color="white"/>
    <stop offset="70%" stop-color="white"/>
    <stop offset="100%" stop-color="black"/>
  </radialGradient>

  <mask id="soft-mask">
    <rect width="100%" height="100%" fill="url(#soft-grad)"/>
  </mask>

  <!-- Checkerboard pattern (something visible to mask) -->
  <pattern id="checker" patternUnits="userSpaceOnUse" width="30" height="30">
    <rect width="15" height="15" fill="#e06c75"/>
    <rect x="15" y="15" width="15" height="15" fill="#e06c75"/>
    <rect x="15" width="15" height="15" fill="#61afef"/>
    <rect y="15" width="15" height="15" fill="#61afef"/>
  </pattern>
</defs>

<!-- Content fades out at edges  -  spotlight/vignette effect -->
<rect width="400" height="200" fill="url(#checker)"
      mask="url(#soft-mask)"/>

4. Luminance vs Alpha Masking

SVG masks default to luminance mode: the brightness of each pixel in the mask determines how visible the masked element is at that point. But there's an alternative - alpha mode - which uses the mask pixel's alpha channel instead.

Where do the "pixels" in a mask come from?

The <mask> element contains normal SVG content - shapes, gradients, text, anything you can draw. The browser renders this content offscreen (you never see it directly). The rendered result is then interpreted as a brightness map:

  1. The browser renders the mask's SVG content to a hidden buffer - shapes, gradients, everything renders as normal.
  2. At each point, the browser reads the brightness of the rendered result.
  3. That brightness becomes the opacity of the masked element at the same position.

So when we say "white pixels" we mean: the areas of the mask's rendered content that you happened to fill with white (or a gradient stop that is white). You're drawing an "opacity map" using the same SVG shapes and gradients from the preceding lessons - but its output is interpreted as brightness values rather than displayed visually.

Concrete examples: what the mask contains → what you see

Mask contains: white circle Mask contains: grey circle (#808080) Mask contains: red circle (#ff0000) ↓ brightness read as opacity ↓ ↓ brightness read as opacity ↓ ↓ brightness read as opacity ↓ 100% visible (white = full luminance) ~50% visible (grey = half luminance) ~21% visible (red has low luminance!)

Top row: what's drawn inside the mask (rendered offscreen). Bottom row: the result - a blue rect masked by each. White = fully visible. Grey = half visible. Red is surprisingly dim because red has low luminance (~0.21) despite being a "bright" colour.

Luminance mode (default)

In luminance mode, the browser computes the luminance (perceived brightness) of each rendered pixel in the mask using the standard formula:

luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B

where R, G, B are in the range 0–1

This formula reflects how the human eye perceives brightness - green contributes the most, blue the least. That's why:

The final mask value at each point is: luminance × alpha. So a semi-transparent white pixel (alpha 0.5) produces 1.0 × 0.5 = 0.5 (half visible). A fully transparent pixel produces 0 regardless of colour.

Gotcha: coloured masks don't work how you'd expect

If you put a bright red shape in your mask thinking "red is a strong colour, it'll make things visible" - you'll be surprised. Red has low luminance (0.21). Use white for full visibility, black for full hiding, and greyscale gradients for smooth fades. Avoid coloured content in luminance masks unless you specifically want the luminance-weighted effect.

What happens in the areas where you drew nothing?

The mask's background is effectively transparent black (rgba 0,0,0,0). Since luminance of black = 0 and alpha = 0, the mask value is 0 × 0 = 0 → fully hidden. So any area of the mask where you didn't draw anything will hide the masked element completely. This is why a white circle on the mask's default empty background creates a "spotlight" - only the circle area is visible.

Alpha mode

In alpha mode, luminance is completely ignored. Only the alpha channel (opacity) of each mask pixel determines visibility:

When is alpha mode useful? When your mask content has coloured shapes (e.g. an imported graphic or icon) and you want their transparency to control the mask, not their brightness. In luminance mode, a red shape would only give ~21% visibility - in alpha mode, the same opaque red shape gives 100% visibility because it's fully opaque.

Luminance mode Alpha mode R: 21% G: 72% B: 7% All: 100% (opaque)

Left (luminance): Red/green/blue circles used as masks - each has different luminance, so the gold rect shows through at different opacities. Right (alpha): Same circles, but in alpha mode - all are fully opaque, so all show 100% regardless of colour.

How to set mask mode

<!-- SVG 2 / CSS approach -->
<mask id="my-mask" style="mask-type: alpha">
  <!-- mask content -->
</mask>

<!-- Or via CSS on the mask element -->
<style>
  #my-mask { mask-type: luminance; }  /* default */
  #my-mask { mask-type: alpha; }      /* alpha mode */
</style>

When to use which

Practical advice: use greyscale in luminance masks

In luminance mode, any colour participates - it gets converted to a brightness number via the formula. But coloured content produces hard-to-predict results (you'd need to mentally compute the luminance to know what visibility a particular colour gives). In practice, stick to greyscale (white, black, and shades of grey) in luminance masks. Grey value maps directly to visibility percentage: 50% grey = 50% visible, 75% grey = 75% visible. No surprises.

Only use coloured content in masks when you're in alpha mode (where colour is ignored and only opacity matters) - or when you specifically want the luminance-weighted effect (rare).

Browser support for mask-type

mask-type is well-supported in modern browsers (Chrome, Firefox, Safari), but older browser versions may not recognise it. If you need alpha masking in environments without mask-type support, you can work around it by using white-coloured shapes with varying opacity inside a luminance mask - since opacity affects luminance calculation.

Ad

5. Clipping vs Masking - When to Use Which

Both achieve "hiding parts of things," but they have different strengths:

Need Use Why
Hard geometric crop (circle, hexagon, polygon frame) clipPath Binary, crisp edges, better performance
Soft fade / gradient transparency mask Supports partial opacity per pixel
Text-shaped window clipPath Text outlines work perfectly as clip geometry
Revealing content with animation mask Can animate mask content (gradient position, shape size)
Complex composition (multiple overlapping translucencies) mask Luminance gives fine-grained per-pixel control

6. Practical Examples

Circular image crop

The most common real-world use of clipPath - cropping a rectangular image to a circle (avatar photos, profile pictures):

Original (rectangular) Clipped to circle

Circular avatar crop - the most common clipPath use case on the web.

<defs>
  <clipPath id="avatar-clip">
    <circle cx="100" cy="100" r="90"/>
  </clipPath>
</defs>

<!-- Clip an image (or any content) to a circle -->
<image href="photo.jpg" x="0" y="0" width="200" height="200"
       clip-path="url(#avatar-clip)"/>

Gradient fade to transparent

Common in hero sections and content edges - content fades smoothly into the background:

Hero Content This content fades out at the bottom

A vertical mask with white (top, visible) fading to black (bottom, hidden) - content smoothly fades into nothing.

Reveal animation concept

Masks are powerful for reveal animations - animate the mask content (e.g. expand a circle or slide a gradient) to progressively reveal the masked element:

Revealed!

A SMIL-animated circle in the mask expands from r=0, progressively revealing the content underneath. (Loops every 3 seconds.)

<defs>
  <mask id="reveal-mask">
    <!-- This circle grows to reveal content -->
    <circle cx="200" cy="150" r="0" fill="white">
      <animate attributeName="r" from="0" to="250"
               dur="2s" fill="freeze"/>
    </circle>
  </mask>
</defs>

<!-- Content is revealed as the mask circle expands -->
<g mask="url(#reveal-mask)">
  <rect width="400" height="300" fill="url(#fancy-gradient)"/>
  <text x="200" y="160" text-anchor="middle">Revealed!</text>
</g>

Combining clipPath with transforms

You can apply transforms to the clip path content, or animate the clipped element's transform for reveal effects. Since clipPath clips in the element's own coordinate space, transforms on the element move the content while the clip stays in place (or vice versa):

Dashed outline = full content area

A sliding clip rect reveals content from left to right. The clip shape's x attribute is animated from -200 to 0. (Loops every 2 seconds.)

<defs>
  <clipPath id="sliding-clip">
    <rect x="0" y="0" width="200" height="300">
      <!-- Slide the clip rect to reveal content -->
      <animate attributeName="x" from="-200" to="0"
               dur="1s" fill="freeze"/>
    </rect>
  </clipPath>
</defs>

<image href="photo.jpg" width="200" height="300"
       clip-path="url(#sliding-clip)"/>

7. Performance Considerations

Rule of thumb: clipPath for hard edges, mask for soft edges

If the boundary between visible and hidden is a crisp geometric line, use clipPath. If you need any form of gradual transition, partial transparency, or feathered edges, you need a mask.

8. The <image> Element

The examples above clip and mask <image> elements - but what is <image>? It embeds a raster image (PNG, JPEG, WebP) or another SVG file into your SVG document, sized and positioned within the coordinate system:

<svg viewBox="0 0 300 200" xmlns="http://www.w3.org/2000/svg">
  <!-- Embed a raster image -->
  <image href="photo.jpg" x="20" y="20" width="260" height="160"
         preserveAspectRatio="xMidYMid slice"/>

  <!-- Clip it to a circle -->
  <clipPath id="avatar">
    <circle cx="150" cy="100" r="80"/>
  </clipPath>
  <image href="photo.jpg" x="20" y="20" width="260" height="160"
         clip-path="url(#avatar)"
         preserveAspectRatio="xMidYMid slice"/>
</svg>
AttributePurpose
hrefURL to the image (relative, absolute, or data URI)
x, yPosition of the top-left corner in the coordinate system
width, heightSize within the SVG (the image scales to fit)
preserveAspectRatioHow the image scales within its box (same values as on <svg>). xMidYMid slice for cover-style cropping.
Left half: unclipped | Visible: clipped to circle

An image (here simulated with a gradient) clipped to a circle using clip-path. The <image> element works identically - just replace the rect with <image href="photo.jpg" ...>.

<image> is a first-class SVG citizen

<image> participates fully in the SVG rendering pipeline. You can clip it, mask it, filter it, transform it, and animate it with SMIL - just like any shape. It uses the same preserveAspectRatio system covered in Lesson 1 to control how the image fits within its bounding box.

Quiz: Check Your Understanding

Question 1

What's the fundamental difference between clipPath and mask?

clipPath uses shapes and mask uses gradients
clipPath is binary (fully visible or hidden); mask supports gradual/partial transparency
clipPath works on groups and mask works on individual elements
clipPath is SVG-only and mask works with HTML elements too

Question 2

In a luminance mask, what colour makes content fully visible?

Black - black means "no masking applied"
Any colour at full opacity - opacity determines visibility
White - maximum luminance means fully visible
Transparent - see-through means the content shows through

Question 3

Can you use <text> inside a <clipPath>?

Yes - the text outline becomes the clipping shape
No - only basic shapes (rect, circle, path) are allowed
Only if you convert the text to a path first
Yes, but only in SVG 2 compliant browsers

Hands-On Exercise

Build these from scratch to cement the concepts:

  1. Circular crop: Create a <clipPath> with a circle and use it to clip a gradient-filled <rect> into a circular shape.
  2. Gradient fade mask: Create a <mask> containing a rect filled with a white-to-black linear gradient. Apply it to a colourful element to produce a smooth fade-to-invisible effect.
  3. Text clip path: Use <text> inside a <clipPath> to create a text-shaped window into a pattern or gradient background.
<svg width="600" height="500" viewBox="0 0 600 500" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- 1. Circular clip -->
    <clipPath id="ex-circle-clip">
      <circle cx="130" cy="80" r="70"/>
    </clipPath>

    <!-- 2. Fade mask -->
    <linearGradient id="ex-fade-grad">
      <stop offset="0%" stop-color="white"/>
      <stop offset="100%" stop-color="black"/>
    </linearGradient>
    <mask id="ex-fade-mask">
      <rect x="20" y="200" width="260" height="120" fill="url(#ex-fade-grad)"/>
    </mask>

    <!-- 3. Text clip -->
    <clipPath id="ex-text-clip">
      <text x="300" y="440" font-size="72" font-weight="900">HELLO</text>
    </clipPath>

    <!-- Shared gradient -->
    <linearGradient id="ex-rainbow" x1="0" y1="0" x2="1" y2="1">
      <stop offset="0%" stop-color="#e06c75"/>
      <stop offset="50%" stop-color="#e5c07b"/>
      <stop offset="100%" stop-color="#61afef"/>
    </linearGradient>
  </defs>

  <!-- 1. Gradient rect clipped to a circle -->
  <rect x="20" y="20" width="260" height="160" rx="6"
        fill="url(#ex-rainbow)" clip-path="url(#ex-circle-clip)"/>

  <!-- 2. Colourful rect with a fade mask -->
  <rect x="20" y="200" width="260" height="120" rx="6"
        fill="url(#ex-rainbow)" mask="url(#ex-fade-mask)"/>

  <!-- 3. Text-shaped window into the gradient -->
  <rect x="20" y="380" width="560" height="100"
        fill="url(#ex-rainbow)" clip-path="url(#ex-text-clip)"/>
</svg>