Gallery✨ Premium

Gallery Flow Morph

Six scattered tiles that drift and follow the mouse; a click turns a tile into a full-frame view, and Escape sends it back to its place.

View TransitionsVanilla JSGalerie

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

An interior designer's mood board — Gallery Flow Morph example 1

① An interior designer's mood board

WhenOn the Lumbrane studio website, a flat's page shows six rooms (living room, kitchen, bedroom, bathroom, study, hallway) as thumbnails scattered over a linen background. Clicking a room enlarges it across the whole board with its name; ✕ or Escape put it back.
WhyThe loose layout recalls a real board pinned to a wall, and the morph keeps the link between the thumbnail and the large image: you always know where what you're looking at came from.
SettingsSix <img> (SVG illustrations) in the tiles, scene without fixed proportions (aspect-ratio: auto, block height), linen background and light viewer (scene CSS), names on a dark label to stay readable. The capture clicks the "Bathroom" thumbnail.
A memory game's end screen — Gallery Flow Morph example 2

② A memory game's end screen

WhenA won game in Souvenelle: on the left, the six pairs found (fox, owl, fish…) float in a 330 × 206 px frame; on the right, the score (6/6, 1:42, 14 moves) and the Play again button. The child opens a card to see it large again.
WhyThe won cards become a small collection to handle: the drift brings them to life, and opening one large rewards the game without leaving the score screen.
SettingsGallery at --gfm-max-width: none and 330/206 format, illustrated cards in <img>, translucent violet frame. The capture opens the "Bee" card.
The moving background of a design biennial — Gallery Flow Morph example 3

③ The moving background of a design biennial

WhenHome page of the Quarvenne biennial: the six exhibition posters cover the whole screen and slide with the mouse; a white box at the top left gives the dates, the number of venues and the Tickets button. Clicking a poster opens it large with the exhibition's title.
WhyThe background isn't a still image: it reacts to the slightest movement and each poster is a way into its exhibition. The opaque box keeps the practical information readable on top.
SettingsFull-height root, square corners, aspect-ratio: auto; box laid over the gallery, tiles moved clear of the box (.gfm-tile:nth-child(n) redefined by the scene CSS) and box hidden while a poster is open. In the capture the mouse draws an ellipse (parallax) then opens the "Grids" poster. The scene copies the tile's displayed name into data-title just before the click, so the title follows the capture's language.

How it works

Inside .gfm-scene (16/10 format, 560 px wide at most), the six .gfm-tile buttons are placed in percentages and rise or sink a few pixels (a 7 s back-and-forth gfm-drift animation). When the pointer moves over the scene, the script sets --px and --py (position from -0.5 to 0.5) and each tile shifts by up to 14 px in the direction given by its --dx / --dy variables: a light parallax, different for each tile.

A click (or Enter) on a tile opens the .gfm-viewer: for the duration of the transition, the tile and the large .gfm-hero image share one view-transition-name, and the View Transitions API animates one into the other in 0.45 s. The title (data-title) appears letter by letter under the image. ✕, a click on the dark background or Escape close it with the reverse movement and return focus to the original tile. Without View Transitions, the viewer fades in and out (0.3 s and 0.25 s).

Tiles accept your images: an <img> in a tile covers it, and the viewer shows the same image large, or the version named in data-full if you provide a heavier one. Without an image, the --art gradient is the visual, as in the demo. Each .fx-0819-root on the page has its own gallery.

Accessibility

  • Tiles by keyboard: they are <button>s named by their text, with a violet :focus-visible outline and the same slight zoom as on hover.
  • Opening and closing: on open, focus goes to ✕ ("Close full-screen view") and the tiles behind become inert; Escape or ✕ close it and focus returns to the clicked tile.
  • Title read in one piece: the animated letters are hidden from screen readers (aria-hidden) and the full title is announced through an invisible copy in the aria-live="polite" caption.
  • Reduced motion: under prefers-reduced-motion: reduce, no drift, no parallax, no morph or fade, no letter-by-letter reveal: the viewer opens and closes directly.
  • Drift pause: a round button at the bottom left (.gfm-pause, "Pause the drift", aria-pressed="true" while it is stopped) stops and restarts the drift by click, touch and keyboard; the drift also pauses while the gallery is hovered and while keyboard focus is inside it. Under reduced motion there is no drift and the button is hidden. Escape only closes the gallery that has focus.

Browser compatibility

document.startViewTransition: Chrome 111, Safari 18, Firefox 144 (according to MDN); elsewhere, a CSS fade. The translate and scale properties (Chrome 104, Firefox 72, Safari 14.1) and inert (Chrome 102, Firefox 112, Safari 15.5). Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two galleries with only the second one driven, opening by keyboard, closing with Escape and focus back on the tile, reduced motion, 390 px without overflow, 0 errors.

Chrome 111+✓ Full, View Transitions morph (measured)
Firefox 144+✓ Full; fade before 144 (measured)
Safari 18+✓ Full; fade before 18 (measured in WebKit)
Edge 111+✓ Full (Chromium engine)
Mobile iOS 18+✓ WebKit engine, not checked on a device
Android Chrome 111+✓ Full (Chromium engine)

Without View Transitions the viewer fades in and out. Without JavaScript the tiles show and drift; neither clicking nor the pause button does anything.

The code

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

index.html — structure
<div class="fx-0819-root">
  <div class="gfm-scene">
    <div class="gfm-field">
      <button class="gfm-tile" type="button" data-title="…" style="--dx: -1; --dy: -0.6"><img src="…" alt=""><span class="gfm-tile-name">…</span></button>
      … (6 tiles)
    </div>
    <button class="gfm-pause" type="button" aria-pressed="false" aria-label="…"></button>
    <div class="gfm-viewer" hidden>
      <figure class="gfm-hero"></figure>
      <p class="gfm-caption" aria-live="polite"></p>
      <button class="gfm-close" type="button" aria-label="…">✕</button>
    </div>
  </div>
</div>

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

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Images (HTML — <img> in .gfm-tile) none: --art gradients The image covers the tile and shows large in the viewer. Keep alt="" if the tile's name is enough.
Large version (HTML — data-full on .gfm-tile) the tile's own image URL of a more detailed image, loaded only on opening.
Title (HTML — data-title) Aurora, Magma, Ocean… Text shown letter by letter under the image and read by screen readers.
Drift and parallax (HTML — --dx / --dy in the tile's style) from -1.2 to 1.1 depending on the tile Direction and strength of the mouse shift (14 px × value) and drift offset.
Pause button (HTML — .gfm-pause) shown at the bottom left, aria-pressed="false" Stops or restarts the drift; change its aria-label when you translate. Only remove it if you also remove the drift.
Maximum width (CSS — --gfm-max-width) 560px The scene takes its container's width up to this limit; none for the full width.
Morph duration (CSS — ::view-transition-old/new(gfm-active)) .45s Speed of the tile → full-frame move.
Tile positions (CSS — .gfm-tile:nth-child(n)) 6 positions in % left / top coordinates and width of each tile; adjust them if you use more or fewer.

FAQ

Yes, but the positions are written for six tiles (.gfm-tile:nth-child(1) to (6)). A 7th tile would land at the top left over the first one: add a :nth-child(7) rule with its place.
The morph uses the View Transitions API (Chrome 111, Safari 18, Firefox 144). Elsewhere the fade takes over: same opening, same closing, without the tile's transformation.
Yes: the round button at the bottom left stops and restarts it (gfm-paused class on .gfm-scene), hover and keyboard focus pause it too, and it disappears under reduced motion. To remove it everywhere, delete the animation: gfm-drift … line from .gfm-tile and the .gfm-pause button: the mouse parallax stays.