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:
- Clipping - visibility determined by geometry: is this point inside or outside a shape boundary? Inside = visible, outside = hidden. That's the only question.
- Masking - visibility determined by pixel values: how bright (or how opaque) is the mask at this point? There's no "inside" or "outside" - just a field of values that control transparency everywhere.
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:
- Define the clip/mask in
<defs>with anid - Reference it on the target element:
clip-path="url(#id)"ormask="url(#id)"
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
- Shapes inside
<clipPath>are geometry only - their fill, stroke, and opacity don't matter. Only their outline (the mathematical boundary) defines the clipping region. - You can use any shape inside a clipPath:
<rect>,<circle>,<ellipse>,<path>,<polygon>,<text>, even<g>groups. - Multiple shapes inside a clipPath create a union - the visible region is anywhere that's inside any of the shapes.
clipPathUnits
userSpaceOnUse(default) - the clip shape coordinates are in the same coordinate system as the element being clipped.objectBoundingBox- coordinates are 0–1, relative to the target element's bounding box. Useful for reusable, scale-independent clips.
Demo: Clipping a rect to a 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:
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
- White pixels in the mask = fully visible (the element shows through completely)
- Black pixels in the mask = fully hidden (the element is invisible there)
- Grey pixels = partially transparent (proportional to brightness)
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
x, y, width, height- the mask region (the area where masking takes effect)maskUnits- coordinate system for x/y/width/height (objectBoundingBoxis default)maskContentUnits- coordinate system for the content inside the mask (userSpaceOnUseis default)
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.
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:
- The browser renders the mask's SVG content to a hidden buffer - shapes, gradients, everything renders as normal.
- At each point, the browser reads the brightness of the rendered result.
- 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
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:
- White (R=1, G=1, B=1) → luminance = 1.0 → fully visible
- Black (R=0, G=0, B=0) → luminance = 0.0 → fully hidden
- 50% grey (R=0.5, G=0.5, B=0.5) → luminance = 0.5 → 50% visible
- Pure red (R=1, G=0, B=0) → luminance = 0.2126 → only ~21% visible (surprisingly dim!)
- Pure green (R=0, G=1, B=0) → luminance = 0.7152 → ~72% visible (greens are bright)
- Pure blue (R=0, G=0, B=1) → luminance = 0.0722 → only ~7% visible (blues are very dark)
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.
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:
- A fully opaque pixel (alpha = 1) → fully visible, regardless of colour (red, blue, white - doesn't matter)
- A fully transparent pixel (alpha = 0) → fully hidden, regardless of colour
- A semi-transparent pixel (alpha = 0.5) → 50% visible
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.
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
- Luminance: Simpler for gradient fades (white-to-black gradients are intuitive). The mental model is "brightness = visibility".
- Alpha: Needed when your mask content has coloured shapes and you want their transparency (not brightness) to control visibility. Also useful when compositing semi-transparent overlapping shapes in the mask.
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).
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.
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):
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:
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:
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):
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
- clipPath is generally faster than mask. Clipping is a simple boolean geometry test (inside/outside), while masking requires per-pixel luminance or alpha calculation from rendered mask content.
- Both create stacking contexts - similar to
opacityorfilter, the browser composites the element separately. - Avoid complex mask content on animated elements if performance matters. Gradients are cheap; large blurred shapes or complex SVG content inside a mask can be expensive to re-render every frame.
- CSS
clip-pathcan reference SVG clipPaths - this bridges CSS and SVG:clip-path: url(#my-svg-clip)works on HTML elements too, giving you SVG's arbitrary-shape clipping in your regular HTML layout. - Prefer clipPath when you don't need soft edges. If your design only needs a hard geometric crop, always reach for clipPath - it's simpler, faster, and more widely GPU-accelerated.
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>
| Attribute | Purpose |
|---|---|
href | URL to the image (relative, absolute, or data URI) |
x, y | Position of the top-left corner in the coordinate system |
width, height | Size within the SVG (the image scales to fit) |
preserveAspectRatio | How the image scales within its box (same values as on <svg>). xMidYMid slice for cover-style cropping. |
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> 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?
Question 2
In a luminance mask, what colour makes content fully visible?
Question 3
Can you use <text> inside a <clipPath>?
Hands-On Exercise
Build these from scratch to cement the concepts:
- Circular crop: Create a
<clipPath>with a circle and use it to clip a gradient-filled<rect>into a circular shape. - 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. - 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>