Zoom Transition
You step into the content: the overview grows to × 2.5 while fading out as the detail comes in from × 0.3 to × 1, both contents changing scale together over 0.7 s. CSS + 0.9 KB of JavaScript, click, Enter or Space, a simple fade in place when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Transitions has 52 effects, including 9 free. Explore the category →
3 usage examples



How it works
The .zmt-frame (280 × 180 px, 16 px corners, overflow: hidden) stacks two .zmt-states with inset: 0, sharing a 0.7 s cubic-bezier(.4, 0, .2, 1) transition on opacity and scale. Overview A (a grid of six thumbnails on slate) sits at scale(1), opaque. Detail B (violet gradient, "ZOOM" title) waits shrunk to a third and transparent: opacity: 0; transform: scale(.3).
When .active toggles, A goes to opacity: 0; scale(2.5) and B arrives at opacity: 1; scale(1). Both contents grow together: A seems to rush toward the eye and dissolve, B rises from the background to take its place — the zoom-in of a map or a gallery. Measured in Chromium 151: 250 ms after the click, A is at × 1.73 and 0.51 opacity, B at × 0.64 and 0.49 (Firefox 153: × 1.69 / 0.54 and × 0.62 / 0.46); at 1.1 s, A at × 2.5 (700 px wide, clipped by the frame) and transparent, B at × 1 and opaque, a state still unchanged at a last reading 3 s later. The second click zooms back out. Same result in WebKit 26.5.
The enlargement starts from A's center (the default transform-origin). Moving that point onto the clicked area changes the story: you no longer step in "in the middle" but into the chosen stand, plot or photo; the FAQ gives the line to add. On the script side, a few lines per .zmt-frame: .active toggled (mouse, Enter, Space), aria-pressed and aria-hidden updated; everything is prefixed zmt-.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, both states stay at scale 1 (transform: none) and only a 0.15 s fade remains (measured in Chromium and Firefox: no scaling, change complete within 260 ms). A zoom rushing toward the eye is one of the most uncomfortable motions for sensitive people. - Keyboard and screen readers: the frame behaves as a toggle button (Enter, Space,
aria-pressed) and the hidden state is not read, as in Slide Transition (fx-0696). - Visible way back: in a real interface, add a "Back to the map" button in state B; people do not always guess that a second click on the detail brings the overview back.
- Text during the zoom: enlarged × 2.5 and half transparent, A's text becomes unreadable; put nothing in A that must be read during the change.
Browser compatibility
CSS (transform: scale(), opacity, transition, inset) + 0.9 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26.5: zoom in, state B held, zoom out on the second click; keyboard and reduced motion measured in Chromium and Firefox. Scale and opacity are animated by the compositor, with no layout recalculation; at × 2.5, A's text is rendered enlarged and may look slightly soft during the change, which is expected. inset sets the floor (Chrome 87, Firefox 66, Safari 14.1).
Without JavaScript, the overview stays on screen. Without inset, write top: 0; right: 0; bottom: 0; left: 0 on .zmt-state.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="zmt-stage">
<div class="zmt-frame" role="button" tabindex="0" aria-pressed="false">
<div class="zmt-state zmt-state-a">
<div class="zmt-grid" aria-hidden="true">…</div>
<span class="zmt-label">…</span>
</div>
<div class="zmt-state zmt-state-b" aria-hidden="true">…</div>
</div>
<span class="zmt-hint">…</span>
</div>
The full HTML + CSS + JS of Zoom Transition, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Zoom point (CSS — transform-origin on .zmt-state-a) |
center (50% 50%) | Set it on the area you enter from: 86% 50% for an item on the right. To set it on click under the pointer, add the line given in the FAQ to the script. |
Zoom strength (CSS — scale(2.5) and scale(.3)) |
A up to × 2.5, B from × 0.3 | × 1.6 and × 0.7 for a soft change between two related screens; × 4 and × 0.1 for a real dive into a map. |
Duration and curve (CSS — transition on .zmt-state) |
0.7 s, cubic-bezier(.4, 0, .2, 1) |
0.5 s for a gallery; an ease-in curve on A and ease-out on B stress the feeling of passing through. |
Contents (HTML — .zmt-state-a, -b) |
thumbnail grid and "ZOOM" title | A is the overview (map, grid, group photo), B the detail. Opaque background on both so neither shows through the other. |
Frame size (CSS — .zmt-frame) |
280 × 180 px | The frame clips A as it grows; resize it freely, the scales are relative. |
FAQ
.active, compute the click position in the frame and write it on A: var r = frame.getBoundingClientRect(); a.style.transformOrigin = ((e.clientX - r.left) / r.width * 100) + '% ' + ((e.clientY - r.top) / r.height * 100) + '%';. The zoom-in then heads for that point, and the zoom-out leaves from it.background: url(…) center / cover) or as an <img> filling the state. At × 2.5 it is shown enlarged for 0.7 s; an image 2.5 times the frame size stays sharp during the change.