Gallery✨ Premium

Swipe Gallery

Viewer built as a track of slides you pull with a finger or the mouse: the photo follows the gesture, then snaps to the next one beyond 50 px.

JSTouchSwipe

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →

3 usage examples

A sneaker brand's lookbook, browsed with the thumb on a phone — Swipe Gallery example 1

① A sneaker brand's lookbook, browsed with the thumb on a phone

WhenIn the Quillerac app, the shopper opens the black Ondrine and pushes the photo left with her thumb: the orange Ondrine comes in, following her finger.
WhyOn mobile, flicking through products is a thumb gesture, not aiming at small arrows; seeing the photo follow the finger feels like a real catalogue.
SettingsTwo-column grid of square thumbnails inside a phone frame, --slb-backdrop: rgba(20, 17, 14, 0.94). Capture: touch-type pointerdown, two pointermove and pointerup sent to .slb-viewport, one per capture frame.
A food truck's menu as posters, pulled with the mouse — Swipe Gallery example 2

② A food truck's menu as posters, pulled with the mouse

WhenOn the Mirabouc site, a customer at the office opens the tacos poster and pulls the track with the mouse to reach the soup of the day.
WhyThe same gesture works with a mouse: no separate desktop version needed, and the cursor leaving the area does not interrupt the drag.
SettingsFour 5:7 thumbnails in a row, next to the week's schedule. Upright posters are shown whole inside 3:2 slides. Capture: the same events, mouse type.
Four-step bowline tutorial followed with the keyboard — Swipe Gallery example 3

③ Four-step bowline tutorial followed with the keyboard

WhenOn the Ferlouse sailing school page, the student opens step 2 and moves to step 3 with the right arrow, hands still on the practice rope.
WhyA step-by-step guide is followed in order and ends at the end: having no loop avoids jumping back to step 1 by mistake, and the counter says where you are.
Settings2 × 2 grid with number and instruction under each diagram, --slb-backdrop: rgba(6, 22, 33, 0.95). Capture: ArrowRight keydown sent to the dialog.

How it works

On the first opening, the script builds one .slb-slide per thumbnail (with the data-full image, otherwise the thumbnail's, and the same alt) and lines them up in .slb-track. Large images are therefore only loaded if someone opens the gallery. The “2 / 4” counter is computed from the number of thumbnails.

Dragging uses Pointer Events with pointer capture: finger, pen and mouse behave alike, and the mouse can leave the area without cancelling the gesture. During the gesture the track follows the pointer with no delay; on release, more than 50 px to the left moves to the next slide, more than 50 px to the right goes back, otherwise the track returns into place with a small bounce (0.4 s). No loop: it stops at the first and the last slide.

A slide is at most 600 px wide, 80 % of the window, or whatever 70 % of its height allows at 3:2; the photo is shown whole (object-fit: contain). Under the track, the “Swipe to navigate” hint swings its arrow three times, then stops.

Accessibility

  • Keyboard: thumbnails are <button> elements; inside the viewer, ← and → change slide, Escape closes. There are no on-screen arrow buttons: with a mouse, you pull the track.
  • Screen readers: only the slide on display is readable (the others carry aria-hidden="true"); the “2 / 4” counter is an aria-live="polite" region.
  • Modal dialog: named “Image viewer”, focus on ✕ on opening, page behind inert, focus returned to the thumbnail on closing.
  • Reduced motion: no zoom on opening, no animated slide between slides, the hint arrow stays still. The gesture stays active: the track still follows the finger.
  • Vertical gesture: touch-action: pan-y lets the page scroll vertically; a vertical move cancels the drag and the track returns into place.

Browser compatibility

Pointer Events and setPointerCapture: Chrome 55, Firefox 59, Safari 13; ResizeObserver realigns the track when the window changes (Chrome 64, Firefox 69, Safari 13.1). The rest follows the viewers' base (modal <dialog>: Firefox 98, Safari 15.4). Measured in Chromium 151, Firefox 153 and WebKit 26.5: mouse drag leaving the area, arrows without loop, touch swipe at 390 px in Chromium.

Chrome 88+✓ Full (measured)
Firefox 98+✓ Full (measured)
Safari 15.4+✓ Full (measured on WebKit)
Edge 88+✓ Full (Chromium engine)
Mobile iOS 15.4+✓ Full (not measured on a device)
Android Chrome 88+✓ Full (touch measured)

Without ResizeObserver, the position is recomputed at each opening but not during a resize. Without showModal(), the gallery stays a row of photos.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0178-root">
  <div class="slb-grid">
    <button type="button" class="slb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Pink to violet gradient" width="600" height="400"></button>
    <button type="button" class="slb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Amber to red gradient" width="600" height="400"></button>
    <button type="button" class="slb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Fuchsia to rose gradient" width="600" height="400"></button>
    <button type="button" class="slb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Violet to cyan gradient" width="600" height="400"></button>
  </div>
  <dialog class="slb-viewer" aria-label="Image viewer">
    <div class="slb-content">
      <p class="slb-counter" aria-live="polite"><span class="slb-current">1</span> / <span class="slb-total">4</span></p>
      <button type="button" class="slb-close" aria-label="Close"><svg>…</svg></button>
      <div class="slb-viewport">
        <div class="slb-track"></div>
      </div>
      <p class="slb-hint"><span>Swipe to navigate</span><svg>…</svg></p>
    </div>
  </dialog>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Change threshold (JS — dx < -50 / dx > 50) 50 px Distance to pull to reach the neighbouring slide.
Slide width (CSS — .slb-viewport) min(600px, 80vw, calc((100vh - 140px) * 1.5)) The vh calculation keeps room, at 3:2, for the counter and ✕ above the image and the hint below it, even in a short window; slides always take 100 % of this width.
Slide format (CSS — aspect-ratio of .slb-slide) 3 / 2 Match it to your photos; change the 1.5 in the width calculation accordingly.
Gap and bounce (CSS — .slb-track) 20 px, 0.4 s cubic-bezier(0.175, 0.885, 0.32, 1.275) The gap is read back by the script, nothing else to change.
Help text (HTML — .slb-hint) “Swipe to navigate” Edit or remove it; the arrow swings three times (animation: slb-hint 1.5s … 3).
Thumbnails and overlay (CSS — --slb-thumb, --slb-backdrop) 120px, rgba(0, 0, 0, 0.9) Width of the page buttons and viewer background.
Large photo (HTML — data-full) absent Image placed in the slide; loaded on the first opening only.

FAQ

Not by default, on purpose. To loop, replace the line Math.max(0, Math.min(slides.length - 1, i)) in show() with (i + slides.length) % slides.length; dragging will then loop too.
That is this gallery's principle: you pull it. The keyboard uses ← and →. If your audience mostly uses a mouse without dragging, prefer the lightbox with navigation, which has arrows.
No: the area allows vertical scrolling (touch-action: pan-y). A vertical gesture lets the page scroll and the track goes back into place.