180

Fisheye Grid Gallery

£7
or the whole Vault, £99
Open in new tab

A photo grid that magnifies like a dock: the photo under the pointer, finger or keyboard focus grows to about twice its size while its rows and columns squeeze to make room, then opens full size from its own cell.

advanced
9 more details
fisheyepointer-trackinggrid-animationtouch-gestureslightboxkeyboard-navigationfocus-managementreduced-motionprogressive-enhancement

Released

About this effect

At rest it is an ordinary, even photo grid: 48 photographs in eight columns and six rows, sized to fill whatever box you give it. Move the pointer over it and the grid behaves like a magnifying dock in two directions at once. The photo underneath swells to about twice its size, its neighbours give way smoothly in its row and its column, and the whole grid stays a perfect tiling: nothing overlaps, no gaps open, and the grid never grows past its box. Each photo is cropped to fill its cell, so as a cell grows you see more of the picture, and a caption fades in on the one you are looking at.

Read the full effect overview

The swell follows a slow pointer closely and trails a fast one, catching up like a liquid lens, and the grid eases back to even when the pointer leaves. Click a photo and it lifts out of its cell and opens full size over the gallery, with the sharp file fading in over the thumbnail; previous and next walk the set, and Escape or Close flies it back into its own cell. Keyboard visitors get the same lens: arrow keys walk a focus cursor cell to cell, the swell follows it, and Enter opens. On a phone, drag a finger across the grid to use it as a loupe and tap to open.

Fisheye Grid Gallery - GSAP animation effect preview

What's included

12 items
  • Two-way fisheye: the photo under the pointer grows while its whole row and column give way, in both directions at once
  • The grid always tiles its box exactly: no overlap, no gaps, constant width and height, whatever the lens is doing
  • Smooth, liquid tracking: close on slow movement, a slight trail on fast sweeps, and an eased return to an even grid
  • Photos are cropped to fill their cells, so a growing cell reveals more of the picture; the caption appears only on the magnified one
  • Click, tap or Enter opens the photo full size from its own cell, with the sharp file fading in over the thumbnail
  • Viewer with previous and next, arrow keys, swipe on touch, a counter and caption, and Escape or Close flying the photo back into its cell
  • Keyboard cursor with a single tab stop: arrow keys, Home and End walk the grid and the lens follows the focus
  • Touch loupe: drag a finger across the grid and the swell follows it; in a longer page a quick vertical swipe still scrolls the page
  • Lens strength, spread, follow speed and column count set from data attributes; columns adapt to the container (8, 6, then 4 on phones)
  • Only small thumbnails load up front; full-size files load when the viewer opens, with the neighbours fetched ahead
  • Reduced motion: an even grid with a quiet focus ring and a crossfading viewer; no JavaScript: a plain grid of links with captions
  • Small API (open, close, next, prev, focus, relax, setMagnify, revert) and focus, open and close events; several galleries per page stay independent

Perfect for

4 use cases
  • Photography portfolios where visitors want to scan dozens of images and still see each one properly
  • Travel, architecture and interiors galleries with a mix of landscape and portrait shots
  • Product, lookbook and press image libraries that should read as a tidy grid until someone explores
  • Event and agency archives that need a gallery to drop onto an existing page without restyling it

How it works

2 sections

The gallery is an ordinary list of links: each one points at the full-size photograph and wraps a small thumbnail and a short caption. Without JavaScript that list is the whole gallery, a grid of links with captions, and with JavaScript it stays the same accessible list while the script takes over its sizing.

The grid is a CSS grid whose column widths and row heights are recalculated many times a second from how far each row and column is from the point you are looking at. Because only the proportions change, the tracks always add up to the same box, which is why the grid can never gap or overlap. GSAP's ticker eases the lens towards the pointer, the finger or the keyboard cursor, and stops running once everything has settled. The viewer is a GSAP timeline that carries the photo from its cell to full size and back. Everything the script adds, from the viewer to the inline grid sizes and listeners, is removed again on revert().

Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

The Vault opens this source and the whole library.

Each product gives you the HTML, CSS and JavaScript source, the full documentation and an AI setup prompt, licensed for unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

One payment, over 350 products, and future additions to the library.

Documentation

Quick Start

1. Add to your HTML <head>:

In your downloadThe working code for this step, in index.html.

2. Add before the closing </body> tag:

In your downloadThe working code for this step, in index.html.

3. Add the gallery anywhere in your <body>:

In your downloadThe working code for this step, in index.html.

Give it a height (see below) and it is done. Each link points at the full-size file, which only loads when the viewer opens it.

Using It With Your Own Design

What the effect needs from your markup: a [data-fisheye-grid] element containing a .fisheye__grid list of .fisheye__cell items, each holding one a.fisheye__item whose href is the full-size image and which wraps an <img> thumbnail. data-width and data-height on the link give the full image's shape, so the viewer can size the photo before the file arrives (without them it uses the thumbnail's shape). The caption is optional: the script reads data-caption on the link, then the .fisheye__caption text, then the image's alt. Any button inside the gallery with data-fisheye-magnify="2.2" (or another number) becomes a lens strength switch with an aria-pressed state; the demo's Soft / Medium / Strong row is just three of those.

What is only the demo's styling: the Hanken Grotesk font, the grey palette, the hint line, the lens control row and its pill styling, the caption chip, the viewer's round buttons and the full-window .showcase-shell layout. Delete or restyle any of it. The tokens at the top of .fisheye set the gutter (--fisheye-gap, 3px), corner radius (--fisheye-radius) and empty-cell colour (--fisheye-cell).

CSS the effect depends on:

  • A height. The grid fills a fixed box and the script only redistributes it. Set --fisheye-height on .fisheye (default min(80vh, 760px)), or give the element a height in your layout and set --fisheye-height: auto as the demo does. The rows share that height evenly at rest.
  • The .has-js .fisheye__grid rules: display: grid, minmax(0, 1fr) tracks, overflow: hidden and the --fisheye-columns container queries (8 columns, 6 below 900px, 4 below 600px of gallery width). Change the breakpoints or counts there, or set data-columns.
  • Cells that clip and photos that cover: .fisheye__cell is position: relative; overflow: hidden and the image is object-fit: cover at 100% by 100%, so a growing cell reveals more of the photo rather than stretching it.
  • touch-action: pan-y pinch-zoom on the grid, which lets the script decide when a finger drag is the loupe (see Touch below).
  • The viewer is drawn inside the gallery element (position: absolute; inset: 0), so .fisheye must stay position: relative.

Options

Set these as data attributes on the [data-fisheye-grid] element.

Attribute Values Default Description
data-magnify 1 to 6 2.2 How large the photo under the pointer grows, in rest cells. With very few columns or rows the size is capped so the others keep a usable sliver (on four columns it is about 2.5).
data-radius 0.4 to 6 1.25 How far the swell spreads, in cells. Smaller is a tighter lens; larger moves more of the grid. It is narrowed automatically when the grid has too few tracks to afford it.
data-columns Any whole number from CSS Fixed column count, overriding --fisheye-columns. Rows follow from the number of photos.
data-follow 1 to 30 11 How quickly the swell catches up with the pointer. Lower trails further behind a fast sweep; higher sticks closer.
data-touch auto, hold, drag auto When a finger drag becomes the loupe. drag: always, and the page never scrolls from the grid (best for a full-screen gallery). hold: after a short hold or a sideways drag, while a quick vertical swipe scrolls the page. auto: drag when nothing around the gallery can scroll, hold otherwise (including inside an iframe).

Accessibility

  • Keyboard: the grid is one tab stop. Arrow keys walk a focus cursor cell to cell, Home and End jump to the ends of the row (with Ctrl, to the first and last photo), and the swell follows the cursor, so keyboard visitors see the same magnified photo and caption. Enter or Space opens the viewer.
  • Viewer: a labelled modal dialog. Focus moves to Close, Tab cycles its three buttons, the rest of the gallery is made inert, arrow keys step through the set, and Escape, Close or a click outside the photo closes it and returns focus to the photo's link. A polite live region announces "20 / 48" and the caption on each step.
  • Screen readers: the gallery stays an ordinary list of links named by each image's alt text; the visible caption is aria-hidden so it is not read twice.
  • Touch: see data-touch. A tap opens the viewer; after a loupe drag the magnified photo holds for a moment and then the grid settles. In the viewer, swipe left or right to step.
  • Reduced motion: no fisheye and no easing. The grid stays even, the focused photo gets a quiet two-tone ring and its caption, and the viewer opens, steps and closes with short crossfades. Changing the preference while the page is open rebuilds the gallery in the other mode.
  • No JavaScript: the gallery is a plain responsive grid of linked thumbnails with their captions, and every photo links to its full-size file.

Photographs

The demo ships 48 photographs as baked WebP derivatives in assets/img/: gNN.webp thumbnails (432 by 324, or 360 by 480 for portrait subjects, about 760 KB for all 48) that load up front, and gNN-full.webp files (1600px on the long edge, at the photo's own shape) that load only when the viewer opens a photo, together with its two neighbours. They are from Pexels and Unsplash and used under the Pexels License and the Unsplash License. Replace them with your own photographs for production; the gallery works with any number.

To regenerate or change them, edit assets/img-manifest.json and run bun scripts/build-template-assets.ts fisheye-grid-gallery from a GSAP Vault checkout, or export your own thumbnails at a size that covers the magnified cell (about 450px wide at the default strength in a 1200px wide gallery).

Photo Subject Source Original
1 Turquoise lake winding through forest Pexels https://www.pexels.com/photo/32837501/
2 Orange facade rolling against a teal sky Pexels https://www.pexels.com/photo/13981699/
3 Portrait in blue and red gel light Pexels https://www.pexels.com/photo/4985860/
4 Stacked bowls in ochre and dark glazes on a shelf Pexels https://www.pexels.com/photo/33367176/
5 Harbour crane and arched bridge reflected at sunset Pexels https://www.pexels.com/photo/19881642/
6 Tall white angular fins rising into a pale sky Pexels https://www.pexels.com/photo/31732696/
7 Rough red garnet crystals on a dark surface Pexels https://www.pexels.com/photo/13307186/
8 Seaside lido at sunset, beach huts beyond Pexels https://www.pexels.com/photo/19847806/
9 Silver bicycle against a red corrugated wall Pexels https://www.pexels.com/photo/28216172/
10 Pine trunks receding into fog Pexels https://www.pexels.com/photo/15222306/
11 Yellow pleated pendant lamps Pexels https://www.pexels.com/photo/20152628/
12 Swimmer in a blue cap beside a lane rope Pexels https://www.pexels.com/photo/3775158/
13 Dark brutalist block against a teal sky Pexels https://www.pexels.com/photo/4328661/
14 Rustic loaves stacked on wooden racks Pexels https://www.pexels.com/photo/30473094/
15 Woman holding an armful of blue delphiniums Pexels https://www.pexels.com/photo/8746996/
16 Ridge rising out of a sea of cloud Pexels https://www.pexels.com/photo/26953466/
17 Curved row of blue sapphires on pale pink paper Pexels https://www.pexels.com/photo/11830966/
18 Photographing the city from a hilltop at dusk Pexels https://www.pexels.com/photo/17929440/
19 Rusted orange curves against a flat blue sky Unsplash https://images.unsplash.com/photo-1486718448742-163732cd1544
20 Flowering heather above a blue sea Pexels https://www.pexels.com/photo/18720264/
21 Man in a yellow roll-neck holding a red vase Pexels https://www.pexels.com/photo/7674636/
22 White spiral gallery ramps under a glass dome Unsplash https://images.unsplash.com/photo-1439337153520-7082a56a81f4
23 Pale blue city car against a stone wall Unsplash https://images.unsplash.com/photo-1549317661-bd32c8ce0db2
24 Rocky cliffs and turquoise surf under cloud Pexels https://www.pexels.com/photo/18729882/
25 Lone viewer among empty red cinema seats Pexels https://www.pexels.com/photo/7991297/
26 Green dunes above the sea under a pink dawn Pexels https://www.pexels.com/photo/5851475/
27 Yellow tracksuit on a sunlit court Unsplash https://images.unsplash.com/photo-1515886657613-9f3515b0c78f
28 Rows of colourful thread cones Pexels https://www.pexels.com/photo/34117399/
29 Radial timber ceiling light seen from below Pexels https://www.pexels.com/photo/5032376/
30 Swimmers in a sea pool beside the railing Pexels https://www.pexels.com/photo/6775404/
31 Weathered carved stone face lit from the side Pexels https://www.pexels.com/photo/31679934/
32 Orange spiral balconies under a dark overhang Pexels https://www.pexels.com/photo/29099985/
33 Sand path through dune grass, from above Pexels https://www.pexels.com/photo/5851474/
34 Rust lounge chair beside a timber coffee table Pexels https://www.pexels.com/photo/2079294/
35 Scaled teal facade sweeping in a curve Unsplash https://images.unsplash.com/photo-1493397212122-2b85dda8106b
36 Woman in a pink shirt holding green cups Pexels https://www.pexels.com/photo/7674847/
37 Lone tree and track in heavy fog Pexels https://www.pexels.com/photo/36344918/
38 Vintage hand drills with red and blue wheels Pexels https://www.pexels.com/photo/15404056/
39 Athlete in yellow reclining on a blue court Pexels https://www.pexels.com/photo/5069201/
40 Brick terrace with a red door and a green one Pexels https://www.pexels.com/photo/2937800/
41 Swimmer in a pink cap resting at the pool edge Pexels https://www.pexels.com/photo/5952863/
42 Orange tiled corner cutting across a blue sky Pexels https://www.pexels.com/photo/5504428/
43 Ring of coffee cups on a round table, from above Unsplash https://images.unsplash.com/photo-1498804103079-a6351b050096
44 Rock coast under a heavy sky Pexels https://www.pexels.com/photo/7112541/
45 Figure with arms wide against a poster wall Unsplash https://images.unsplash.com/photo-1523398002811-999ca8dec234
46 Slatted timber globe pendant from below Pexels https://www.pexels.com/photo/23020540/
47 Calm sea meeting a straight beach at dusk Pexels https://www.pexels.com/photo/5851478/
48 Orchard rows with crates of red apples Pexels https://www.pexels.com/photo/2965607/

Dependencies

Required:

  • GSAP 3.12+ (core only). One GSAP ticker eases the lens and stops once it has settled; GSAP timelines carry the photo into and out of the viewer; GSAP's context and media queries handle the reduced-motion rebuild and revert(). The demo is pinned to GSAP 3.15.0.

No plugins, no Lenis and no other libraries.

Browser Support and Performance

All current browsers (CSS grid, container queries and the inert attribute). Each frame of the lens rewrites two grid-template values on one element, so the cost is one layout of the grid's cells; it stays smooth with 48 photos and comfortably beyond. Nothing runs while the grid is at rest. Keep thumbnails small (around 10 to 25 KB each), since all of them load up front, and keep full-size files for the viewer.

Also in your download: the full source, worked examples, the events and programmatic API, and the class reference.

£7