Clip-Path and Masks
When to Use
Use
clip-pathfor shaped containers, animated reveals (wipes, sweeps, iris), comparison sliders, and creative transitions. It is compositor-accelerated and produces no layout shifts. Usemask-imagewhen you need soft edges instead of hard clip boundaries.
Decision
| Situation | Choose | Why |
|---|---|---|
| Animate a reveal (wipe, sweep, iris) | clip-path from hidden to visible state |
Compositor-only, smooth 60fps |
| Reveal content on scroll | clip-path + IntersectionObserver class toggle |
Cleaner than opacity-fade for directional reveals |
| Diagonal or angled section break | clip-path: polygon() on the section |
No extra pseudo-elements needed |
| Complex SVG shape mask | clip-path: url(#svg-clippath) |
Arbitrary shapes, curves, text masks |
| Shadow on a clipped shape | filter: drop-shadow() on the parent |
box-shadow is clipped; drop-shadow follows the shape |
| Fade content at an edge | mask-image with gradient |
Clip-path creates hard edges; mask creates soft ones |
Animation Rules
- Animate between
polygon()values — must have the same number of points - Animate
inset()toinset()freely - Cannot animate between shape types (e.g.,
circle()topolygon())
Pattern
Wipe reveal on scroll:
.reveal-wipe {
clip-path: inset(0 100% 0 0);
transition: clip-path var(--duration-slow) var(--ease-emphasized-decel);
}
.reveal-wipe.is-visible { clip-path: inset(0 0% 0 0); }
Diagonal section break:
.diagonal-section { clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%); }
Animated iris reveal:
@keyframes iris-open {
from { clip-path: circle(0% at 50% 50%); }
to { clip-path: circle(150% at 50% 50%); }
}
.iris-reveal { animation: iris-open var(--duration-slow) var(--ease-emphasized-decel) both; }
Mask-image for soft edge fade:
.fade-bottom {
mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
}
.fade-sides {
mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
}
Clip-path comparison slider:
.comparison { position: relative; --divider: 50%; }
.comparison .before { position: absolute; inset: 0; clip-path: inset(0 calc(100% - var(--divider)) 0 0); }
slider.addEventListener('input', (e) => {
comparison.style.setProperty('--divider', `${e.target.value}%`);
});
Common Mistakes
- Wrong: Animating between different point-count polygons — Right: Browser cannot interpolate; the shape snaps
- Wrong: Expecting
box-shadowon clipped elements — Right: Usefilter: drop-shadow()on a wrapper instead - Wrong: Using
clip-pathfor layout-affecting shapes — Right: Clipped content still occupies its original space; useshape-outsidefor text wrapping - Wrong: Forgetting pointer events — Right: Elements outside the clip region are hidden but still receive pointer events; add
pointer-events: noneif needed
See Also
- Entrance Animations — clip-path reveals as alternatives to opacity fades
- Animation Performance —
clip-pathis compositor-only - Blend Modes and Visual Effects — combining clip with blend for creative effects
- Reference: Emil Kowalski: The Magic of Clip-Path
- Reference: CSS-Tricks: Animating with Clip-Path
- Reference: Sara Soueidan: CSS and SVG Clipping