Zoom lightbox
A picture viewer mounted by one attribute on a group of pictures.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe look of the preview is thezoom-lightbox-*classes of the paste. The code is longer than one Webflow Embed holds, so the paste brings two: this stylesheet and the settings are the first, the script of the next step the second. The first Embed holds the look of the viewer, and reads five variables,--zoom-lightbox-ground(the sheet behind the picture),--zoom-lightbox-ink,--zoom-lightbox-accent,--zoom-lightbox-label-fontand--zoom-lightbox-pad: set them on the group, or on anything above it, in a rule of your own. The caption takes the typeface of the group. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-zoom-lightbox Marks the group of pictures. It takes no value: leave the value field empty. Every picture inside it opens in the viewer, in the order of the page, one picture or forty, any shape. The settings below go on the same element. data-zoom-lightbox-caption On a picture, with a few words as its value: the caption of that picture in the viewer, in place of its alt. Left empty, that picture has no caption. Without it the caption is the figcaption around the picture, or its alt. [ Optional ] data-zoom-lightbox-src On a picture, with the address of a larger file as its value: what the viewer lays over the picture of the page once it has arrived. Without it the viewer takes the file a link around the picture leads to, or the largest candidate of its srcset, which is what a Webflow Image carries, or the picture of the page itself. [ Optional ] data-zoom-lightbox-item On a few pictures of the group, with no value: only those open in the viewer and the others are left alone. Without it on any picture, every picture of the group opens. [ Optional ] data-zoom-lightbox-away Written by the engine on the thumbnail whose picture is in the viewer, so its place stays empty; each picture that opens also carries data-zoom-lightbox-trigger, the group data-zoom-lightbox-ready, and the viewer data-zoom-lightbox-state (opening, open, dragging, closing) and data-zoom-lightbox-ui while its words are shown. Never written by hand; the stylesheet reads them, and so can a rule of yours. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-zoom-lightbox-duration 0.95 The time the picture takes from its thumbnail to full size, in seconds. The way back, the change of picture and the words follow it. data-zoom-lightbox-arc 0.5 How much the trip bows away from the straight line, and how much the picture leans into it. data-zoom-lightbox-loop on Whether the last picture leads to the first. Options: on, Next on the last picture goes to the first and Prev on the first to the last; off, the two ends stop, and the button that leads nowhere is dimmed. data-zoom-lightbox-close Close The word of the button that closes the viewer. Options: any short word, in the language of the page: Fermer, Schliessen. data-zoom-lightbox-prev Prev The word of the button that goes to the picture before. Options: any short word, in the language of the page: Avant. data-zoom-lightbox-next Next The word of the button that goes to the picture after. Options: any short word, in the language of the page: Suivant.
Two files and their pictures (18), no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
ZoomLightbox.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-zoom-lightbox-duration 0.95 The time the picture takes from its thumbnail to full size, in seconds. The way back, the change of picture and the words follow it. Quicker; under 0.6 the curve and the ground are hardly seen; 0 opens at once Slower and more shown; over 1.4 the viewer feels late on the click data-zoom-lightbox-arc 0.5 How much the trip bows away from the straight line, and how much the picture leans into it. Straighter; 0 is a straight line with no lean A wider curve and more lean; 1 is the most data-zoom-lightbox-loop on Whether the last picture leads to the first. Options: on, Next on the last picture goes to the first and Prev on the first to the last; off, the two ends stop, and the button that leads nowhere is dimmed. data-zoom-lightbox-close Close The word of the button that closes the viewer. Options: any short word, in the language of the page: Fermer, Schliessen. data-zoom-lightbox-prev Prev The word of the button that goes to the picture before. Options: any short word, in the language of the page: Avant. data-zoom-lightbox-next Next The word of the button that goes to the picture after. Options: any short word, in the language of the page: Suivant. -
Drive it from JavaScript
When a button elsewhere opens the gallery, when the pictures change after load, or when the page follows which picture is shown.const element = document.querySelector('[data-zoom-lightbox]'); element.addEventListener('zoomlightbox:shown', (e) => { console.log(e.detail); }); // Follow the picture in the viewer: an analytics call, an address to update. element.addEventListener('zoomlightbox:shown', (event) => { console.log(event.detail.index, event.detail.item); }); const viewer = ZoomLightbox.mount(element); // the group auto() mounted, or a new one viewer.open(2); // the third picture leaves its thumbnail // Once a picture is shown: viewer.next(), viewer.prev(), viewer.go(5) and viewer.close(). // When the gallery leaves the page: ZoomLightbox.destroy(element); // removes the viewer, gives the markup back as it was
-
Fit it to your page
The pictures
- Mark the group, and that is all:
data-zoom-lightboxon the block that holds the pictures. One picture or forty, upright, wide or square, in a grid, a row or a text: eachimgin the group opens, and Prev and Next follow the order of the page. With one picture the count and the two buttons are not shown. - A thumbnail may be cropped by the page:
object-fit: cover, anobject-position, an aspect ratio, a small radius. The picture leaves from exactly that window and opens to its whole shape on the way. - The caption is the
altof the picture. To say something else, putdata-zoom-lightbox-caption="Your words"on the picture; leave the attribute empty for no caption. Afigcaptionin afigurearound the picture is read before the alt. - A larger file, three ways. Wrap the picture in a link to it,
<a href="https://example.com/large.jpg"><img src="https://example.com/small.jpg" alt=""></a>: without JavaScript the link still opens the file. Or give the picture asrcset: the viewer asks for the candidate that suits the whole screen, which is what a Webflow Image does by itself. Or name it,data-zoom-lightbox-src="https://example.com/large.jpg". In every case the viewer shows what the page already has, at once, and lays the large file over it when it is decoded: the trip never waits and nothing blinks. - A picture inside a link to a page is left alone: the link keeps leading to its page.
- Several galleries on a page each work on their own, each with its own settings and colours.
The keyboard, the mouse, the finger
Tab reaches each thumbnail; Enter or Space opens it. In the viewer the focus is on Close and Tab goes round the three buttons without leaving. Left and Right change the picture, Home and End go to the first and the last, Escape closes, and the focus returns to the thumbnail of the picture that was open.
A click on the picture goes to the next one; a click beside it closes. A drag across the picture, with the mouse or a finger, goes to the next or the one before. A drag up or down takes the picture off its ground, which shrinks around it; let go far enough or fast enough and it flies back to its thumbnail, let go early and it returns to the middle.
The colours and the sizes
Five custom properties, set at zero specificity on the group. Give them your own values on the group or on anything above it; the viewer, which is added at the end of the page, reads them from its group each time it opens, and takes the typeface of the group for the caption.
--zoom-lightbox-ground: the sheet behind the picture, warm black in the preview. A light ground works as well.--zoom-lightbox-ink: the count, the caption and the three words.--zoom-lightbox-accent: a word under the pointer, and the focus ring.--zoom-lightbox-label-font: the typeface of the count and of the three words.--zoom-lightbox-pad: the margin of the viewer, around the words and around the picture.
From JavaScript
ZoomLightbox.mount(element, overrides)returns the instance of a group, mounting it if it was not:{ element, settings, items(), index(), state(), open(which, options), close(options), go(which, options), next(), prev(), refresh(), destroy() }.opentakes a number counted from 0 or a thumbnail element, and{ instant: true }to skip the trip, or{ hold: 0.5 }to stop it at that share of the way, for a still;close()goes on from there.ZoomLightbox.auto(root)mounts every marked group insideroot, and runs once on its own;ZoomLightbox.destroy(element)gives the markup back.- Events, all bubbling from the group:
zoomlightbox:ready, fired during the mount, so before any listener added after the script tag (readitems()on the instance instead);zoomlightbox:openas the picture leaves its thumbnail;zoomlightbox:changeas soon as another picture is asked for, with{ index, previous, item };zoomlightbox:shownonce a picture is in place;zoomlightbox:closeonce it is back in its thumbnail. - Pictures added after the mount are taken in by
refresh(). - **Under
prefers-reduced-motion: reduce** the viewer opens, changes and closes at once, with no trip and no wipe. - Without JavaScript the gallery is the page's own pictures, and a picture wrapped in a link opens its file.
- The photographs and the words of the demo are the demo's: on a real page every picture and caption is the user's own, or asked for.
- Mark the group, and that is all:
-
Avoid the pitfalls
- The attribute goes on the group, not on each picture. A group marked twice, once inside the other, is two galleries.
- **Give each picture its
widthandheightattributes**, as for any image: a thumbnail that has not loaded yet, lazy and far down the page, then still opens in a frame of the right shape. - **The viewer is added at the end of
body**, in a nativedialog, so a rule of the page that styles everydialogor everybuttonreaches it. Its own classes all start withzoom-lightbox-. - The stylesheet places the viewer. Without
zoom-lightbox.cssthere is no ground and the pictures are not placed, and the engine says so in the console. - A thumbnail that moves under the pointer, scaled on hover for one, is read where it is at the click, so the picture leaves from the scaled window. Keep such a hover small.
- In Webflow the code is two Embed blocks, pasted together at the foot of the section: the settings and the stylesheet in the first, the script in the second. Keep both, the first above the second.
- In Webflow use plain Image elements in the group, not Webflow's own Lightbox element, which brings its own viewer. A Webflow Image carries a
srcset, so the viewer asks for a large enough file by itself. - The page behind is held still by
overflow: hiddenon thehtmlelement, with a padding the width of the scrollbar so nothing jumps. A bar fixed to the right edge of the page is not inside that padding: give it the same room if a shift of a few pixels shows. - A caption is one or two lines. It sits at the foot of the screen, beside the two buttons; a long paragraph pushes the picture up.
- In React or any framework that owns the DOM, mount in an effect and call
destroy()in its cleanup; after the list of pictures changes, callrefresh().
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Zoom lightbox is a site utility component for Webflow and vanilla JavaScript, with no library. Also called image lightbox gallery, lightbox custom or lightbox zoom from thumbnail.
A picture viewer mounted by one attribute on a group of pictures. A click on a thumbnail lifts that very picture off the page: it travels to the middle of the screen on a bow, leaning a little into the trip, and opens out of the crop its thumbnail had to its whole shape, while its dark ground spreads from the edges of the thumbnail to the edges of the screen as a sharp sheet, never a fade. The count, the caption and three words, Close, Prev and Next, come up from under their own feet once the picture is nearly there. From one picture to the next the frame stays: it takes the shape of the next picture while a line crosses it, the picture that leaves on one side and the one that comes on the other. On the way out the picture goes back to its thumbnail, the ground closing on it, and the page has been scrolled behind the ground so that the thumbnail is there to receive it. The picture can be dragged: across for the next one, up or down to take it off its ground and let it go home, with the mouse or a finger. A native dialog: focus kept inside, Escape, arrows, Home and End, the page held still without a sideways jump, the focus given back to the thumbnail. One picture or forty, any shape, a caption read from the alt, an attribute or a figcaption, a larger file from a link or a srcset laid over the small one when it has arrived. Reduced motion respected. No library. Pattern type lightbox, image viewer, gallery zoom.
Updated
Questions
Select the block that holds your Image elements and add the custom attribute data-zoom-lightbox with an empty value. Every image inside it opens in the viewer, in the order of the Navigator, one image or forty, any shape. The code goes in two Embeds, which the paste brings with it.
Yes, that is what this one does. The thumbnail itself leaves its place, travels to the middle of the screen on a curve and opens out of the crop the page gave it, object-fit: cover included, while a dark ground spreads from its edges. On the way out it flies back to the same thumbnail.
Yes. A drag across the picture goes to the next one or the one before, and a drag up or down takes the picture off its ground and sends it back to its thumbnail, with a finger or the mouse. Let go early and it returns to the middle.
The caption is the alt of the image. To say something else, put data-zoom-lightbox-caption on the image with your words, or leave that attribute empty for no caption. A figcaption in a figure around the image is read as well.
Yes. It opens in a native dialog: the focus stays among its three buttons, Left and Right change the picture, Home and End go to the first and the last, Escape closes, and the focus returns to the thumbnail. The page behind is held still without jumping sideways, and under reduced motion everything opens and closes at once.
Yes, three ways: a srcset on the image, which a Webflow Image already carries, a link around the image that leads to the large file, or data-zoom-lightbox-src. The viewer shows the file the page already has at once and lays the large one over it when it is decoded, so nothing blinks.