Masked Gradients as Hover Physics
CSS image effects often need extra markup, pseudo-elements, or a mix of both. The experiments here do neither. Using a single <img> element and CSS masks paired with gradient backgrounds, we can build hover transitions that wipe away a frame or crop an image into slices — all without touching the document structure.
The technique relies on one core idea: the <img> itself doubles as the container. Padding on the image lets a gradient background show through, and a CSS mask hides everything except that padded frame. Then, changing the mask’s shape, position, or size on hover reveals the image beneath. The result is a reveal animation driven entirely by CSS variables and composited gradients.
The markup stays identical in every demo:
<img src="" alt="">
The same pattern repeats across the examples: gradient masks layered onto the image, combined with mask-composite, then animated by altering the mask’s geometry. The code below uses standard CSS properties; browser support for mask-composite is inconsistent, so prefixed versions are included in the live demos for broader coverage.
Hole-Punch Reveal
The first effect starts with a square image framed by a gradient border. The frame is produced by a repeating linear gradient applied as the <img> background, with padding exposing it. Two mask layers then isolate the frame:
img {
/* etc. */
mask:
linear-gradient(#000 0 0) padding-box,
linear-gradient(#000 0 0) content-box;
}
The content-box mask maps to the image’s content area; the padding-box mask spans the full padded area. Compositing with exclude removes the overlap, leaving only the frame visible. The two layers and their compositing rule:
img {
/* etc. */
mask:
linear-gradient(#000 0 0) padding-box,
linear-gradient(#000 0 0) content-box;
mask-composite: exclude;
}
That produces a visible gradient frame around a hidden image. Alternatives exist: a conic-gradient mask can punch the same hole without mask-composite. But compositing avoids hardcoding the padding value, so it is the approach I prefer.
Replacing the content-area mask with a radial-gradient creates a circular cutout:
See the Pen [Adding the radial-gradient](https://codepen.io/smashingmag/pen/wvQOJmb) by Temani Afif.
The exclude value cuts a hole in the image. Scaling the radius up shrinks the visible area; shrinking the radius expands it. Setting the mask size to 141% fully covers the image, so the hover state can transition that value down to zero for a wipe-in effect. The complete CSS for the hover:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
radial-gradient(#000 70%, #0000 71%) content-box
50% / var(--_s, 150% 150%) no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
--_s: 0% 0%;
}
That compresses to a few rules: a CSS variable holds the initial mask size, hover reduces it, and a .5s transition smooths the change. Because the mask position can be shifted as easily as the size, a variation of the same effect fades in from a different corner:
See the Pen [Another variation of the circular reveal animation](https://codepen.io/smashingmag/pen/dyQrvEq) by Temani Afif.
Swiping Gradients
A second effect uses a linear gradient instead of a radial one. The size becomes 200% 200%, and hover moves the mask position from 0% 0% to 100% 100% instead of changing its dimensions:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(135deg, #000 50%, #0000 0) content-box
0% 0% / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
mask-position: 100% 100%;
}
Reversing the gradient angle to -45deg and swapping the hover position inverts the swipe direction:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(-45deg, #000 50%, #0000 0) content-box
100% 100% / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
mask-position: 0% 0%;
}
Only one position value is declared for two mask layers. Since the first gradient occupies its full area, percentage-based positions cannot move it; that value only affects the second layer.
Combining the two swipe directions produces an effect where both gradients start at the center (50% 50%) — one covering the top-left, the other the bottom-right. Hover slides both outward. The three-layer mask introduces a complication, though. Simply stacking the gradients leaves a faint diagonal seam from anti-aliasing:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(135deg, #000 50%, #0000 0) content-box
50% 50% / 200% 200% no-repeat,
linear-gradient(-45deg, #000 50%, #0000 0) content-box
50% 50 / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
cursor: pointer;
}
img:hover {
mask-position: 0% 0%, 100% 100%;
}
Adjusting the gradient stop from 50% to 50.5% makes the artifact worse. The fix is to change how the mask layers composite. Declaring exclude on the first layer alone isn’t enough; the second and third layers must first be combined with add before being excluded:
img {
mask:
/* 1st layer */
linear-gradient(#000 0 0),
/* 2nd layer */
linear-gradient(135deg, #000 50.5%, #0000 0) content-box
50% 50% / 200% 200% no-repeat,
/* 3rd layer */
linear-gradient(-45deg, #000 50.5%, #0000 0) content-box
50% 50% / 200% 200% no-repeat;
mask-composite: exclude, add;
}
This eliminates the seam. A further subtlety: the hover state declares two mask-position values, but the effect uses three mask layers. CSS repeats the value list to cover the missing layer, so the first value also applies to the third gradient.
Reverse-Engineering the Zig-Zag
A third demo shows zig-zag edges sliding apart over the image. The effect feels more complex, but it depends on the same physics discussed above — gradient shapes as masks plus layer compositing. The full implementation is left as an exercise, though two resources explain how to build the sawtooth edge: a mask-based border technique and a generator for custom borders.
Gradients as Cutout Stencils
These experiments demystify a confusing part of CSS. Masks behave like stencils: gradients taught us to think of them as cutout templates, and compositing tells the browser how overlapping stencils interact. Animating a reveal is then only a matter of moving or scaling one layer’s geometry, all within the bounds of a single image tag.
See the Pen [Hover reveal animation using mask IV](https://codepen.io/smashingmag/pen/PoxLMOy) by Temani Afif.
See the Pen [Hover reveal animation using mask V](https://codepen.io/smashingmag/pen/jOQJgxN) by Temani Afif.
See the Pen [Hover reveal animation using mask VI](https://codepen.io/smashingmag/pen/gOQEVdQ) by Temani Afif.



