074

Lenticular Card

£5

Tilt a ridged card to reveal an alpine lake from day to night, with optical interlacing, moving plastic highlights and a physical settle.

advanced
4 more details
webgl-shader3d-transformshover-effectclick-toggle
Lenticular Card - GSAP animation effect preview

This demo reads better at your own screen size than in the frame below:

About this effect

A tactile lenticular-card effect that reveals a second image as the viewing angle changes. The default alpine landscape changes from daylight to a starry night; an alternate guardian-artwork preset is included. A three.js shader interlaces the images beneath a moving plastic highlight, while GSAP plays the view changes and soft release.

What's included

8 items
  • Two selectable image pairs: alpine lake day/night and dormant/awakened guardian
  • Lenticular image switching with clean artwork at either angle extreme
  • Ridged optical highlights and restrained perspective tilt
  • Pointer movement and horizontal touch drag through the same interaction
  • Accessible artwork buttons and instant left/right keyboard selection
  • Optional idle sweep with a documented off switch
  • Composed fallback for reduced motion, unavailable WebGL or missing dependencies
  • Responsive canvas, offscreen pause and complete GPU teardown

Perfect for

4 use cases
  • Travel and destination pages with matched day-to-night landscapes
  • Collectible product launches with tactile lenticular-print previews
  • Digital trading-card collections with an alternate artwork reveal
  • Artist portfolios with paired illustrations in one interactive object

How it works

1 section

Two decoded source images become textures on a three.js card. The fragment shader compares viewing angle with each cylindrical lens phase to select the visible artwork and adds a narrow travelling reflection. The render loop smooths pointer input with elapsed-time decay; GSAP tweens the view target for button changes and release easing, while GSAP also handles motion preferences and teardown.

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

Lighthouse, as measured

Google Lighthouse on this effect's demo, 9 September 2026. 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.

Paid effect

Purchase to unlock the code.

Buying Lenticular Card opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£5 Standard license, unlimited projects

Browse free effects

Documentation

Quick Start

  1. Copy assets/ into your project. Keep both images local and serve the page over HTTP or HTTPS.
  2. Add the stylesheet and this synchronous fallback probe to your HTML <head>. The probe hides the source card only when a WebGL scene can run. Its timeout restores the source if a dependency never arrives.

Code snippet omitted: it ships with the download.

  1. Add the card markup inside your HTML <body>. Each source image is decoded once and reused for its GPU texture. Change the image descriptions to match your content.

Code snippet omitted: it ships with the download.

  1. Add the dependencies before your closing </body> tag. The classic effect script is appended after the module has loaded; it also runs when the document is already parsed. Load Mona Sans as shown in the demo, or change the two font declarations in makeTexture() alongside your own CSS.

Code snippet omitted: it ships with the download.

Already using three.js? Assign your imported namespace to window.THREE and load the effect once. Keep one three.js version in the page rather than adding another copy. script.min.js can replace script.js in production.

Using It With Your Own Design

The contract is one [data-lenticular] root, one [data-lenticular-source] figure, and exactly two [data-lenticular-image] images in viewing order. Optional [data-lenticular-show="0"] and [data-lenticular-show="1"] buttons must be inside the root. The script queries these data attributes, so the decorative class names may change with your CSS.

Give the root a real width and height, position: relative, and touch-action: pan-y. The inserted canvas fills it absolutely and ignores pointer events. Place your controls above it with position: relative or absolute and z-index: 2. Keep the source card fully composed before JavaScript, then hide it only under html.gl or the root's .is-live. The opacity gate is essential: a child whose visibility is restored for alternate artwork must not appear through a hidden source figure. Never hide the fallback under .has-js.

The grey page, shadow oval, toolbar, prompt and spacing are demo CSS and can be replaced. The framed card graphic is assembled in makeTexture(): this small function draws your image, the dark caption scrim, border, title, subtitle and orange mark onto a local texture. Replace that composition there for a different card design; update the fallback figure to match. Keep the title metadata and figure caption aligned. No effect logic depends on the landscape, guardian artwork or captions. The optional assets/demo.js sets the chosen demo images and labels before the renderer initializes; omit it and the preset picker when supplying your own pair.

Demo Presets

The demo opens with Landscape: Alpine Lake, with Day and Night view buttons. Choose Guardian in the labelled Artwork selector to reuse the same card with the preserved Kogen illustrations and Dormant/Awakened buttons. ?artwork=guardian opens that preset directly; an absent or unknown artwork value opens Landscape. Changing the selector reloads the same stage with that query value, preserving other URL parameters. The thumbnail always uses Landscape.

The selector is optional demo code in assets/demo.js, loaded after the source markup and before the dependency/module shim. Its [data-lenticular-preset] select and [data-lenticular-preset-control] label choose the preset; [data-lenticular-caption], [data-lenticular-subcaption] and [data-lenticular-controls] identify the demo caption and control-group text it updates. The core renderer queries none of these demo hooks. Without JavaScript the default daylight card is visible and the inactive preset picker stays hidden.

Options

Attributes go on the [data-lenticular] root. Numeric zero values are respected.

Attribute Values Default Description
data-auto 0, 1 1 Sweeps the viewing angle after 3.5 seconds without input. Set to 0 for a still stage or deterministic captures. This option is also read while running.
data-ridges 40300 160 Number of cylindrical lens bands across the card. Higher values are finer.
data-light 02 1 Strength of the travelling plastic highlight; 0 disables it.
data-title Text Kogen Title painted on both card textures.
data-subtitle Text The ember guardian Small caption painted on both textures.

The demo explicitly supplies Alpine Lake and From daylight to starlight; the renderer's unconfigured fallback captions remain the guardian defaults above. Set texture-related options before initialization. Update captions through a fresh instance after changing them; they are painted once, not every frame.

Choosing Your Images

Use two portrait images at a 2:3 aspect ratio, ideally 1024×1536 or greater. The included frame draws the whole image into the same fixed portrait rectangle, so crop other aspect ratios beforehand to prevent stretching. Align the mountain ridgeline, horizon and reflection, or a character's eyes, face and silhouette. The effect should reveal a clear change in lighting, costume or state without a large jump in subject position.

The transition runs horizontally through fine vertical strips. Large shapes and clear tonal differences survive that interlacing better than tiny unrelated details. The same alpine lake in daylight and under stars gives a strong, readable light change. The retained pale dormant and orange awakened guardian pair demonstrates the same mechanism with illustration. No colour grading is required. Keep images same-origin, or configure image CORS correctly before drawing them into a texture.

The included alpine scenes and guardian illustrations are original generated assets. Their provenance and prompts are in assets/img-src/PROMPTS.md. The JPEG files are masters; the WebPs in assets/img/ are the browser assets.

Accessibility

Pointer movement and horizontal touch drag share the same viewing input. The card captures an active pointer and releases it on pointerup or cancellation; vertical touch gestures remain available to the page. Left/right arrow keys select the current pair immediately when the card or its view buttons have focus. The native Artwork select keeps its normal keyboard behavior; pointer interaction with the select does not tilt or drag the card. Focus on the stage demonstrates the alternate view, and the real buttons expose selection through aria-pressed.

Reduced motion creates no WebGL canvas and runs no idle sweep. The composed source card remains visible, and buttons or arrow keys switch the static images. No JavaScript or unavailable WebGL also leaves the source card visible; without JavaScript the first image is the static presentation.

Device orientation was deliberately omitted: horizontal touch drag already maps precisely to the optical viewing angle without permission or iframe sensor policies. Motion sensors are not required for any part of the interaction.

Browser Support

The animated surface requires WebGL 2, ES modules, import maps and modern pointer events. Current browsers supporting these APIs run the effect; restricted graphics contexts and failed library loads retain the static card. A network error may still appear in a browser's developer console, while the fallback remains usable. Test from a local server rather than opening the HTML as a file: URL.

Performance

Pixel ratio is capped at 2 on fine pointers and 1.5 on coarse pointers. The shader samples two textures and shades the lens bands in a single pass; no post-processing pipeline is allocated. ResizeObserver tracks the root rather than the window, and rendering pauses offscreen or in a hidden tab. Lower the pixel-ratio caps in initialize() first if a slower device needs less work. Texture size is the next lever.

The continuous pointer chase uses elapsed-time decay and clamps long frames. GSAP tweens only the view target for click changes and the 600ms release; the render loop reads that target and owns the optical surface and 3D pose.

Dependencies

Dependency Role
GSAP 3.12+ (demo uses 3.15.0) Artwork-change and release target tweens, gsap.context() and gsap.matchMedia() lifecycle. No plugins.
three.js r180 Renderer, camera, card meshes, textures and custom optical shader.
Mona Sans Demo typography and the caption painted onto the textures; a system font fallback is supported.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

Everything in the catalogue, plus everything we release next.