Image Zoom
Pure CSS — hovering the container triggers scale(1.15) on the image, clipped by overflow:hidden; a semi-transparent scrim fades in alongside.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cards has 41 effects, including 6 free. Explore the category →
3 usage examples



How it works
The effect relies on a CSS pair. The card carries overflow:hidden: when the image scales up it overflows logically, but is visually clipped to the card edges. Hovering (:hover img) applies transform:scale(1.15), animated by transition:transform .5s. No layout change occurs — the card does not move, only the image pixels are scaled.
An overlay layer (.zoom-overlay) is positioned absolute; inset:0 with opacity:0 at rest. On hover it reaches opacity:1 via an independent transition (opacity .3s), faster than the zoom — this speed offset gives the fade a snappier feel. The rgba(0,0,0,0.3) backdrop and centred text live entirely in HTML, with no JavaScript.
No JavaScript is involved. The effect applies to any <img> with object-fit:cover inside a fixed-size container — changing the card's size is enough; the zoom stays proportional.
Accessibility
- prefers-reduced-motion ABSENT: no
@media (prefers-reduced-motion:reduce)rule in the code — thetransform .5stransition runs even when the OS signals a motion-reduction preference. Add manually:@media (prefers-reduced-motion:reduce) { .card-image-zoom img { transition:none } }. - :focus-within absent: zoom triggers on
:hoveronly. A keyboard user tabbing to the card does not see the zoom. Add.card-image-zoom:focus-within img { transform:scale(1.15) }for keyboard coverage. - The
altattribute is present in the demo code (alt="Mountain") — replace with a real description in production for screen reader users. - Overlay contrast:
rgba(0,0,0,0.3)background + white text — ratio ≈ 7:1 on a mid-dark background, WCAG AA compliant. - The card is a
<div>withoutroleoraria-label— if clickable (link to an article or product), use a native<a>or addrole="button"andtabindex="0".
Browser compatibility
Uses only CSS3: transform, transition, overflow, object-fit. No JS or external dependency.
Without transition support, the hover state changes instantly but remains functional. Without object-fit (very old browsers), the image may distort — mitigate with fixed width/height on the <img> element.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="card-demo card-image-zoom">
<img src="your-image.jpg" alt="Image description">
<!-- Optional: scrim + hover text -->
<div class="zoom-overlay">
<span>Optional text</span>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| scale(1.15) | 1.15 | Zoom factor on hover. 1.08 = very subtle, 1.15 = standard, 1.25 = dramatic. Edit in the .card-image-zoom:hover img rule. |
| transition: transform .5s | .5s | Zoom animation duration. 0.3s = snappy, 0.5s = smooth, 0.8s = cinematic. Edit in .card-image-zoom img. |
| rgba(0,0,0,.3) | .3 | Scrim background opacity on hover. 0 = invisible (remove the overlay block), 0.5 = clearly visible. Edit in .zoom-overlay. |
| transition: opacity .3s | .3s | Scrim fade duration, independent from the zoom duration. Differentiate the two for a temporal stagger effect. |
| --radius-xl | 1rem | CSS variable — card corner radius. 0 = square, 0.5rem = slight, 1.5rem = very rounded. |
| --bg-card | #12121a | CSS variable — card background visible while the image loads or if it fails. |
FAQ
data- attribute exposed. The value scale(1.15) is a constant in the .card-image-zoom:hover img rule. Edit it directly in your stylesheet or override with a more specific selector (.my-card.card-image-zoom:hover img { transform:scale(1.20) }).:hover behaves differently by browser: a tap may trigger the hover state on iOS Safari (persisting until the next tap elsewhere), but not reliably on Android Chrome. For consistent touch behavior, complement with :focus-within or add touchstart/touchend JS listeners.<div class="zoom-overlay"> from the HTML — the image zoom works independently of the overlay. You can also keep the block and add display:none in your CSS, then reveal it dynamically via JS if needed.