medium-zoom Article Image Zoom — Free JS Snippet

medium-zoom Article Image Zoom with Hi-Res Swap · Media · Plain HTML, CSS & JS · Live preview

What's included

Features

One-line setup: mediumZoom(selector) makes every match zoomable
Smooth scale transition from the image's exact position
Hi-res swap via data-zoom-src, fetched only when zoomed
Dark overlay with configurable colour and margin
Scroll-to-dismiss with a scrollOffset threshold
Escape and click-to-close, no layout shift when opened
opened/closed events and getZoomedImage() for custom behaviour
zoom-in cursor cue and explicit z-index handling

About this UI Snippet

medium-zoom Article Image Zoom — HTML, CSS & JavaScript

Screenshot of the medium-zoom Article Image Zoom with Hi-Res Swap snippet rendered live

A lightbox is the wrong tool for an image inside an article. It yanks the reader into a separate mode, hides the text they were reading and asks them to find their way back. What readers want is the behaviour Medium made famous: click a figure, it grows smoothly from exactly where it sits to fill the screen over a dimmed backdrop, and any scroll or click puts it back. medium-zoom is a tiny library that reproduces that, and a single call — mediumZoom('.mz-img') — makes every matching image zoomable.

The efficiency trick is the data-zoom-src attribute. Article images should be small so the page loads fast, but a small image looks blurry when enlarged to full screen, which defeats the purpose of zooming. medium-zoom solves this by letting each image name a second, higher-resolution file; the enlarged copy starts from the thumbnail and swaps in the original as soon as it loads. The reader pays the bandwidth cost only if they actually zoom. The snippet demonstrates this by rendering the same artwork twice, at 600 and 1800 pixels, and the status line reports the natural pixel width of the zoomed image so the swap is observable.

The library returns an instance with a small event API. opened and closed are used here to update the status message, and getZoomedImage() exposes the enlarged element. Other lifecycle events exist for open, close, detach and detached if you need to pause videos or track analytics. Options control the feel: margin sets the space around the enlarged image, background sets the overlay colour, and scrollOffset sets how far a reader can scroll before the zoom closes on its own — the behaviour that makes it feel like part of the page rather than a modal.

A few styling details prevent classic problems. The zoomed image and overlay get explicit z-index values so sticky headers cannot cover them, and the cursor is set to zoom-in on the thumbnails to signal that they are interactive. Escape closes the zoom, and because the original image stays in the document flow, the layout never jumps when it opens.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to make every image inside your post content zoomable automatically, add a caption inside the zoomed view, or lazy-generate the hi-res source URL from the thumbnail.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build click-to-zoom article images with medium-zoom 1.1 loaded from a CDN.

Requirements:
- Create an article with two figures whose <img> elements have a small src and a data-zoom-src pointing to a larger file.
- Call mediumZoom('.mz-img', { margin: 28, background: 'rgba(12,14,26,.92)', scrollOffset: 40 }).
- Use zoom.on('opened') and zoom.on('closed') to update a status line showing zoom.getZoomedImage().naturalWidth.
- Add cursor: zoom-in on the images and explicit z-index values for the overlay and opened image.
- Generate both image sizes on canvases so the demo needs no files.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

Requires
<article class="mz-article">
  <p class="mz-kicker">Design notes</p>
  <h2>Why the best galleries do almost nothing</h2>
  <p>Readers do not want a gallery inside a blog post. They want to <em>glance</em> at a chart, decide it matters, and enlarge it without losing their place. Click any figure below to zoom it in place.</p>

  <figure>
    <img class="mz-img" id="mzA" alt="Sunrise gradient chart">
    <figcaption>Figure 1 &mdash; a 600px thumbnail that swaps to a 1800px original when zoomed.</figcaption>
  </figure>

  <p>The trick is that the small image in the flow of the article stays small and cheap, and only the enlarged view fetches the heavy file.</p>

  <figure>
    <img class="mz-img" id="mzB" alt="Ocean gradient chart">
    <figcaption>Figure 2 &mdash; press Escape, scroll, or click again to dismiss.</figcaption>
  </figure>

  <div class="mz-status" id="mzStatus" role="status" aria-live="polite">Nothing zoomed yet.</div>
</article>

Step by step

How to Use

  1. 1
    Read the articleTwo figures sit inline as light 600-pixel thumbnails.
  2. 2
    Click a figureIt scales up from its exact position to fill the screen over a dark backdrop.
  3. 3
    Check the status lineIt reports the natural pixel width of the zoomed image — the 1800-pixel original, not the thumbnail.
  4. 4
    Dismiss itPress Escape, click again, or scroll a little. It shrinks back into place.
  5. 5
    Try the second figureZoom the other chart to see the same behaviour with a different source image.

Real-world uses

Common Use Cases

Blog posts and documentation
Let readers enlarge diagrams and screenshots without leaving the page. For full gallery browsing use the PhotoSwipe viewer.
Charts and infographics
Keep charts small in the article flow but readable at full size on demand.
Case studies and portfolios
Show detailed design mock-ups inline and let visitors inspect the pixels.
Learning performance patterns
A practical example of deferring heavy assets until the user shows intent.

Got questions?

Frequently Asked Questions

Add data-zoom-src="large.jpg" to the image. medium-zoom swaps to it after opening, so the large file is only downloaded if the reader zooms.

Avoid wrapping zoomable images in links, or use a separate trigger; otherwise the click navigates instead of zooming.

Pass a background option, for example mediumZoom(".img", { background: "rgba(0,0,0,.9)" }).

Yes. Call zoom.attach(newImages) on the instance, or create a new mediumZoom call for them.

Use zoom.on("opened", handler) and zoom.on("closed", handler), or the open and close events for the start of each transition.

Yes. Tapping an image zooms it, and tapping or scrolling dismisses it.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from medium-zoom, so in a framework project install it with npm install medium-zoom instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit after the images render, and release it with detach() when the component unmounts.