A lightweight lightbox,
in a single file.
Zoom, pan, touch gestures, keyboard navigation, accessible labels — all packed into one dependency-free JavaScript file. CSS inlined, no build step, framework-agnostic.
Quick start
Add the script, bind a selector, done.
<!-- Include (or download and self-host) --> <script src="https://litezoom.dev/litezoom.min.js" defer></script> <!-- Bind: article images open on click; full mode adds zoom / pan / thumbnails --> <script> LiteZoom.bind('.post-content img', { mode: 'full' }); </script> <!-- Or skip the JS: declare it on the include tag (here: no thumbnail strip, file name as caption) --> <script src="https://litezoom.dev/litezoom.min.js" defer data-bind=".post-content img" data-mode="full" data-thumbs="false" data-caption="filename"></script>
https://litezoom.dev/litezoom.min.js
Demo
This page uses LiteZoom itself — click any image. Scroll or pinch to zoom, drag to pan, pull down to close.
Keyboard: Esc close · ← → switch · + − zoom · double-click toggles 1x / 2x
Why LiteZoom
Trade-offs made for content sites, not a feature contest.
Single-file delivery
CSS inlined, SVG icons inline. No build chain, no peer dependencies — copy one file into any project and it works.
Two modes
simple just opens and switches, great for feeds; full adds zoom, pan, a thumbnail strip and captions for long-form articles. The thumbnail strip, caption and counter can each be toggled on their own.
Full gestures
Wheel and pinch zoom, drag to pan, swipe to switch, pull down to close — one Pointer Events implementation for desktop and touch.
Accessibility done right
Focus returns to the trigger on close; every aria label is localizable; respects prefers-reduced-motion.
Never hijacks real links
Images wrapped in links to other pages navigate as usual; links to large images load the hi-res original. Use data-litezoom for extension-less URLs.
Lazy-load enhancer
Ships enhance(): IntersectionObserver lazy loading + blur-in fade, sharing a single binding with the lightbox.
API
| Method | Description |
|---|---|
LiteZoom.bind(sel, opts) | Delegated binding: images matching sel open on click. opts.mode is 'simple' | 'full'; group(img) returns a group key for gallery navigation; exclude(img) returns true to skip. UI options in the next row. |
thumbs / caption / counter | UI options for both bind and open, overriding the mode default: thumbs toggles the bottom thumbnail strip (on in full); caption is false | 'alt' | 'title' | 'filename' or a function img => text (shown in full when provided); counter toggles the top-left counter (on by default). |
<script data-*> | No JS needed: set global defaults on the include tag with data-mode / data-thumbs / data-caption / data-counter, and bind directly with data-bind="selector". Precedence: bind / open options > tag attributes > mode defaults. |
LiteZoom.open(items, i, opts) | Open manually: items is an array of {src, thumb?, caption?}, i the start index. For React / Vue apps that handle clicks themselves. |
LiteZoom.close() | Close the current lightbox. |
LiteZoom.enhance(sel, opts) | Image enhancement: lazy loading (data-src) with blur-in fade; set opts.zoom to true to also bind the lightbox. |
LiteZoom.refresh(root) | Re-apply enhancement to images inserted dynamically. |
data-litezoom | Explicit marker, takes priority over the extension heuristic: on an <a> it means "the href is an image, open the lightbox" (for extension-less CDN URLs); as <img data-litezoom="hi-res URL"> it names the hi-res source directly, no wrapping link needed. |
LiteZoom.labels(map) | Override accessibility labels (Chinese by default): {viewer, prev, next, zoomIn, zoomOut, close, error, thumb(i)}. |