GalleryFree

Hover Zoom

Your image grows gently inside its frame when the mouse passes over it or when the surrounding link gets keyboard focus, without a line of JavaScript.

CSSTransform

Updated

Hover or click the scene to interact

This effect is free — the Gallery category contains 44 effects total, including 6 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

① Hiking journal cards — the photo moves closer on hover

WhenOn Talmerine, three hiking stories sit side by side as cards: photo, title, one sentence, level, time and climb. Hovering anywhere on a card brings the photo closer and underlines the title.
WhyThe slight move closer says “this card opens” without another button; the effect sits on the photo, the part of the card that makes you want to go.
SettingsEach card is a link wrapping the frame (.hzm-frame as a <div>), --hzm-width: 100%, --hzm-ratio: 16/10, --hzm-radius: 8px, --hzm-scale: 1.12.
Lirandelle Workshop — work to markTab through the submissions
Taches d'aquarelle

Wet washes — Clara M.

Watercolour · exercise 3 of 8 · sent yesterday

To mark
Lettres Ag en didone

Didone contrast — Yanis B.

Typography · exercise 4 of 8 · sent this morning

New
Vase en terre cuite

Centring the clay — Maëlle R.

Ceramics · exercise 2 of 8 · sent Monday

Marked 15/20

② Student work in an online art school — marking with Tab

WhenIn the teacher area of Lirandelle Workshop, each submission fits on one row: thumbnail of the work, exercise and student, progress through the course, and a “To mark”, “New” or “Marked 15/20” label. Tabbing through the list, the focused thumbnail moves forward.
WhyTeachers often mark from the keyboard, one submission after another: on a small thumbnail, the zoom works as an indicator and shows where you are in the list, by keyboard or mouse, with no extra highlight box.
Settings.hzm-frame as a link, --hzm-width: 128px, --hzm-ratio: 16/9, --hzm-scale: 1.18, --hzm-duration: .35s; light-blue focus outline added by the scene.
Rayonnages de livres colorés
12th edition

Brumerande Book Fair

21 – 23 March · Covered market · 140 publishers · free entry Sunday

Tickets

ProgrammeSigningsGetting there

③ Book fair banner — the shelves come forward

WhenThe Brumerande Book Fair page opens on a full-width banner of shelves with a “12th edition” label, then the fair's name, the dates and the “Tickets” button.
WhyOn a large image, a slow, small zoom adds depth without distracting from the practical information.
Settings.hzm-frame as a <div> (image not clickable), --hzm-width: 100%, --hzm-ratio: 540/200, --hzm-radius: 0, --hzm-scale: 1.08, --hzm-duration: 1.2s.

How it works

The .hzm-frame clips the overflow (overflow: hidden) and holds your <img class="hzm-image">, cropped like a cover photo (object-fit: cover). On hover the image goes to scale(var(--hzm-scale)), that is 1.3, over --hzm-duration (0.5 s) with a curve that slows down at the end.

By default the frame is an <a> link: replace href="#" with your address. The zoom also triggers when the link gets keyboard focus, and when a link wrapping the frame, for instance a whole article card, is hovered or reached with Tab. If the image is not clickable, change the <a> into a <div>: hover remains, focus no longer applies.

Everything is CSS tied to .fx-0179-root and the hzm- prefix: as many copies as you like on a page, no clash with your own classes. The frame takes the available width up to --hzm-width (280 px) and keeps --hzm-ratio.

Accessibility

  • Keyboard: the link takes Tab focus and the zoom shows with a white focus outline (:focus-visible). In Safari, Tab only reaches links when “Press Tab to highlight each item” is on (Option + Tab otherwise).
  • Touch: there is no hover with a finger; mobile browsers apply hover at the moment of the tap, just before following the link. The zoom is only a visual cue, nothing is hidden behind it.
  • Screen readers: the link is named by the image's alt text; the “Hover Me” caption is hidden (aria-hidden).
  • Reduced motion: under prefers-reduced-motion: reduce, the transition is removed; the image goes straight to its enlarged size.

Browser compatibility

CSS only: transform, object-fit, aspect-ratio, :focus-visible (in a separate rule so hover does not break in browsers that lack it). Measured in Chromium 151, Firefox 153 and WebKit 26.5: two independent copies, zoom on hover and keyboard focus, reduced motion without transition, no overflow at 390 px.

Chrome 88+✓ Full (measured)
Firefox 89+✓ Full (measured)
Safari 15+✓ Full (WebKit engine measured)
Edge 88+✓ Full (Chromium engine)
Mobile iOS 15+✓ Full (WebKit engine measured) — keyboard focus from 15.4
Android Chrome 88+✓ Full (Chromium engine)

Before Safari 15.4, the keyboard-focus zoom does not apply (hover does). Without aspect-ratio, the frame takes the image's height.

The code

Copy the two blocks (HTML and CSS) into your page. No JavaScript, no dependencies.

HTML
<div class="fx-0179-root">
  <a class="hzm-frame" href="#">
    <img class="hzm-image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='400' viewBox='0 0 560 400'%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop stop-color='%23667eea'/%3E%3Cstop offset='.5' stop-color='%23764ba2'/%3E%3Cstop offset='1' stop-color='%23f093fb'/%3E%3C/linearGradient%3E%3Cpattern id='s' width='20' height='20' patternUnits='userSpaceOnUse' patternTransform='rotate(45)'%3E%3Crect x='10' width='10' height='20' fill='%236366f1' fill-opacity='.1'/%3E%3C/pattern%3E%3Cpattern id='q' width='20' height='20' patternUnits='userSpaceOnUse'%3E%3Cpath d='M0 .5H20M.5 0V20' stroke='%23fff' stroke-opacity='.1'/%3E%3C/pattern%3E%3CradialGradient id='l' cx='168' cy='120' r='241' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23fff' stop-opacity='.3'/%3E%3Cstop offset='1' stop-color='%23fff' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='d' cx='392' cy='280' r='241' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-opacity='.2'/%3E%3Cstop offset='1' stop-opacity='0'/%3E%3C/radialGradient%3E%3Crect width='560' height='400' fill='url(%23g)'/%3E%3Crect width='560' height='400' fill='url(%23s)'/%3E%3Crect width='560' height='400' fill='url(%23q)'/%3E%3Crect width='560' height='400' fill='url(%23l)'/%3E%3Crect width='560' height='400' fill='url(%23d)'/%3E%3C/svg%3E" alt="Violet gradient grid pattern" width="560" height="400">
    <span class="hzm-label" aria-hidden="true">Hover Me</span>
  </a>
</div>
CSS
/* fx-0179 — Hover Zoom: the image grows inside its frame on hover or keyboard focus (CSS only).
   Everything is scoped to .fx-0179-root and the hzm- prefix: two copies on one page stay independent. */
.fx-0179-root,
.fx-0179-root *,
.fx-0179-root *::before,
.fx-0179-root *::after {
  box-sizing: border-box;
}

.fx-0179-root {
  --hzm-scale: 1.3;          /* zoom level on hover / keyboard focus */
  --hzm-duration: 0.5s;      /* zoom-in and zoom-out time */
  --hzm-width: 280px;        /* maximum frame width (the frame shrinks on small screens) */
  --hzm-ratio: 7 / 5;        /* frame shape */
  --hzm-radius: 0.75rem;     /* frame corners */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 250px;
  padding: 16px;
  position: relative;
  background: #0a0a0f;
  color: #fff;
  font-family: sans-serif;
}

/* Frame: fluid width (capped by --hzm-width), height from --hzm-ratio */
.hzm-frame {
  position: relative;
  display: block;
  width: 100%;
  max-width: var(--hzm-width);
  aspect-ratio: var(--hzm-ratio);
  border-radius: var(--hzm-radius);
  overflow: hidden;
  isolation: isolate;
  background: #12121a;
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.hzm-frame:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* Your image: any <img>, cropped to the frame like a cover photo */
.hzm-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-user-select: none;
  user-select: none;
}

/* Optional caption over the image (delete the span if you do not want it) */
.hzm-label {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 600;
  font-size: 1.2rem;
  text-shadow: rgba(0, 0, 0, 0.3) 0 2px 4px;
  pointer-events: none;
}

.hzm-frame {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

.hzm-image {
  transition: transform var(--hzm-duration) cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Hover on the frame or on a link that wraps it */
.hzm-frame:hover .hzm-image,
.fx-0179-root a:hover .hzm-image {
  transform: scale(var(--hzm-scale));
}
/* Keyboard focus on the frame (a link) or on a link that wraps it (separate rule: older browsers skip it alone) */
.hzm-frame:focus-visible .hzm-image,
.fx-0179-root a:focus-visible .hzm-image {
  transform: scale(var(--hzm-scale));
}

/* Reduced motion: the image still zooms, without the transition */
@media (prefers-reduced-motion: reduce) {
  .hzm-image {
    transition: none;
  }
}
JavaScript

Pure CSS — no JavaScript needed.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Image (HTML — src of .hzm-image) violet grid pattern (inline SVG) Your photo and its alt, which also names the link.
--hzm-scale 1.3 Enlargement on hover; 1.05 to 1.15 for cards, 1.3 for a strong effect.
--hzm-duration 0.5s Duration in and out; 1s or more for a large image.
--hzm-width · --hzm-ratio · --hzm-radius 280px · 7 / 5 · 0.75rem Maximum frame width (it shrinks on small screens), proportions and rounding.
Frame tag <a href="#"> Put in your address; or use a <div> if the image is not a link, or place the frame inside a larger link (a whole card).
.hzm-label caption “Hover Me” Text centred on the image; delete the <span> on a real photo.

FAQ

Is JavaScript needed?

No. It all lives in the CSS: :hover and :focus-visible trigger the transform, the frame clips the excess.

How do I zoom when the whole card is hovered?

Wrap the frame and the text in one link placed inside .fx-0179-root: the .fx-0179-root a:hover .hzm-image rule enlarges the image as soon as the card is hovered.

Does the image get blurry?

Only if the file is exactly the frame's size. Use an image 1.3 times wider (or the value of --hzm-scale).