Clip-Path Reveal Animations: Wipes, Irises and Corners Without Extra Markup
A reveal draws attention to content arriving — a heading wiping in, an image opening from a point, a panel sliding into view — without the element itself moving. Opacity fades are soft and forgettable; translations move the element and can trigger layout concerns. Clipping sits between them: the element stays exactly where it is while the visible region grows. The narrow problem this page solves is building those reveals with clip-path alone, getting the shapes to interpolate rather than jump, triggering them on hover, focus or scroll, and giving reduced-motion users an equivalent result. It belongs to Clip-Path & Mask Animations in the CSS-Only Micro-Interactions & Animations guide.
Why clip rather than move or fade
Three properties can make content "appear", and they communicate different things. opacity says the content was always there and is fading into focus. transform: translate() says the content travelled from somewhere. clip-path says the content is being uncovered — a curtain opening, a window widening — which suits reveals of content that should feel anchored in place.
Clipping also has practical advantages. The element's box never changes, so nothing around it reflows, and its final position is its real position from the first frame, which keeps focus rings, anchor targets and measurement code honest. And because the unclipped content is fully rendered underneath, there is no flash of different layout when the animation ends.
The accessibility caveat is the same as for any motion: large reveals across much of the screen are more intense than small fades, and users who ask for reduced motion should receive the content without the sweep.
The shapes and how they interpolate
clip-path accepts basic shape functions. Only matching shapes interpolate smoothly, so the design of a reveal starts with choosing a shape that can express both its start and end states.
inset()clips to a rectangle inset from each edge, with optional rounded corners viaround. It is the workhorse for wipes: animate one edge from100%to0.circle()andellipse()clip to a round shape with a position. Animate the radius from zero for an iris or spotlight. To fully cover a rectangle from its centre, the radius must reach the corners, which is about 71% of the element's diagonal reference;75%is a safe round number.polygon()clips to arbitrary straight-edged shapes and interpolates point by point, so both keyframes must list the same number of points in the same order.
The complete implementation
The page below shows three reveals: a heading that wipes in on load, a card image that irises open on hover or keyboard focus, and a diagonal panel reveal.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Clip-path reveals</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 1.5rem; display: grid; gap: 1.5rem; }
/* 1. Wipe: the right inset shrinks from 100% to 0. The heading's box is
full width from the start, so nothing around it moves. */
.wipe {
font-size: 2rem;
margin: 0;
animation: wipe-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes wipe-in {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
/* 2. Iris on hover and focus: the image starts as a small circle and
opens to cover the card. A link wraps the card, so keyboard focus
triggers the same reveal via :focus-visible. */
.card { display: block; width: 16rem; color: inherit; text-decoration: none; }
.card__img {
height: 9rem;
border-radius: 10px;
background: linear-gradient(135deg, #22d3ee, #6366f1);
clip-path: circle(18% at 50% 50%);
transition: clip-path 0.45s ease;
}
.card:hover .card__img,
.card:focus-visible .card__img {
clip-path: circle(75% at 50% 50%);
}
.card:focus-visible { outline: 2px solid #2563eb; outline-offset: 4px; border-radius: 12px; }
/* 3. Diagonal: four points in both states, so it interpolates.
The start state collapses the right edge into a sloped line. */
.panel {
padding: 1.5rem;
background: #0f172a;
color: #f8fafc;
clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
animation: diagonal-in 0.8s ease-out 0.3s both;
}
@keyframes diagonal-in {
to { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
}
/* Reduced motion: no sweeping. Show the end state immediately. */
@media (prefers-reduced-motion: reduce) {
.wipe, .panel { animation: none; clip-path: none; }
.card__img { transition: none; }
}
</style>
</head>
<body>
<h1 class="wipe">Field notes</h1>
<a class="card" href="/trips/ridge/">
<div class="card__img" role="img" aria-label="Ridge at dawn"></div>
<p>Ridge at dawn</p>
</a>
<section class="panel"><p>Panel content revealed on a diagonal.</p></section>
</body>
</html>
The both fill mode on the load animations is essential. Without backwards fill, the element renders unclipped for the duration of the delay, then snaps to the clipped start state when the animation begins — a flash of the final content before it is hidden and revealed. animation-fill-mode: both applies the first keyframe during the delay and keeps the last one afterwards; the details are covered in Keyframe Animation Patterns.
The key technique: interpolating point by point
Polygon interpolation is literal: point one moves to point one, point two to point two. That makes the start polygon a design tool. The diagonal reveal starts with all four points on the left edge — a zero-width shape — and each point travels to its corner. Change which corner each point starts on and the reveal changes character without changing the end state.
To tilt the leading edge, give the two moving points different progress at an intermediate keyframe — for example 50% { clip-path: polygon(0 0, 70% 0, 40% 100%, 0 100%); }. The top point leads and the bottom point follows, producing a diagonal sweep. Every keyframe must still list four points.
When a design calls for a transition between visibly different shapes — a triangle becoming a hexagon — pad the simpler shape with duplicate points so both have the same count. That technique is developed in Morphing Shapes With clip-path polygon().
Timing a reveal so it reads well
A reveal is only as good as its timing, and clip-based reveals have characteristic timing needs that differ from fades.
Duration scales with the area uncovered. A wipe across a short heading reads well at 400 to 600 milliseconds; the same duration on a full-width hero image feels rushed because the eye has to track a much longer edge. Allow roughly 700 to 900 milliseconds for large surfaces and keep small reveals, such as an icon or badge, under 300. The broader reasoning is in Choosing Animation Durations.
Ease out, not in. A reveal should move fastest at the start, when there is nothing to read yet, and slow down as the last content is uncovered so the reader's eye catches up. A strong ease-out such as cubic-bezier(0.22, 1, 0.36, 1) does that. Ease-in reveals feel sluggish at first and then slam shut at the end, exactly when the eye wants to settle.
Hover reveals should reverse faster than they open. When the pointer leaves, the content is no longer the focus of attention, so a slow closing iris feels laggy. A transition can have different timings in each direction by setting a shorter transition-duration on the base state and a longer one on the :hover state: the declaration on the state being entered governs the transition.
Stagger reveals rather than stacking them. Several elements revealing simultaneously compete for attention. Offset each with a small animation-delay — 60 to 100 milliseconds per item — so the eye moves through them in order. Custom properties make this systematic, as shown in Staggered List Animations With Custom Properties.
Variation: scroll-triggered reveals
Reveals are most often wanted as content scrolls into view. With scroll-driven animations, the same keyframes can be bound to the element's own passage through the viewport, with no observer script.
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.reveal-on-scroll {
animation: wipe-in linear both;
animation-timeline: view();
/* Start as the element enters; finish when 40% of its travel is done. */
animation-range: entry 0% cover 40%;
}
}
}
Because progress now comes from scroll position, the timing function should be linear — easing would distort the mapping between scroll distance and reveal. Wrapping the rule in @supports and the motion query means browsers without scroll timelines, and users who prefer less motion, see the content unclipped.
Browser support
Animating clip-path basic shapes — inset(), circle(), ellipse() and polygon() — is supported in current versions of Chrome, Edge, Firefox and Safari. Scroll-driven animations are supported in Chrome and Edge 115+ and Safari 26+, and remain unshipped in Firefox, where the @supports guard leaves the content visible. prefers-reduced-motion is supported in Chrome 74+, Edge 79+, Firefox 63+ and Safari 10.1+.
FAQ
Which clip-path shapes can be animated?
Two shapes of the same type interpolate: inset() to inset(), circle() to circle(), ellipse() to ellipse(), and polygon() to polygon() with the same number of points. Animating between different shape functions, or polygons with different point counts, jumps instead of interpolating.
Is animating clip-path fast?
It is cheaper than animating layout properties because the element's size and position never change, but in most engines clip-path animation still repaints on the main thread. Keep reveal animations short and on modest elements, and prefer transform or opacity for anything that loops.
Does a clipped element still receive clicks outside the visible shape?
No. Hit testing respects clip-path, so pointer events only reach the visible region. That is usually desirable for reveals, but it means a partly revealed button can be hard to click until the animation finishes.
Why does my inset() reveal look different in right-to-left layouts?inset() takes physical edges: top, right, bottom and left. A wipe written from left to right continues to run left to right in RTL text. Swap the values under :dir(rtl) if the reveal should follow the reading direction.
Related
- Clip-Path & Mask Animations — the parent guide.
- Fade Edges With mask-image — soft-edged reveals instead of hard ones.
- Morphing Shapes With clip-path polygon() — shape-to-shape transitions.
- Aspect Ratio for Responsive Media — reserving the box a reveal plays inside.
Related articles
More pages in the same section.