Funhouse Portrait

£5

Inflate noses, squeeze faces and twist smiles with a playful three.js portrait filter that springs back on release.

advanced
4 more details
webgl-shaderfisheyehover-effectdraggable
Funhouse Portrait - GSAP animation effect preview

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

About this effect

A playful portrait filter effect that turns a photograph into a movable funhouse mirror. Hold over a nose, an eye or a smile to grow a smooth local distortion, then drag it across the face. Three.js renders the photo lens; GSAP handles the press, elastic release and transitions between three distinct filters.

What's included

8 items
  • Inflate, Squeeze and Twist lenses with visibly different shapes
  • Press-and-hold intensity with a bouncy release and subtle radial ripple
  • Touch dragging and a full keyboard interaction
  • Two bundled portrait photographs with a compact image switch
  • Configurable lens radius, strength and optional mobile drift
  • Natural photo colour with no lighting or colour-fringe overlay
  • Composed photo fallback for reduced motion, no JavaScript and unavailable WebGL
  • Documented cleanup for single-page application routes

Perfect for

4 use cases
  • Creative studio portraits with an unexpected personal touch
  • Playful campaign microsites with an immediately shareable interaction
  • Photo booths with a choice of exaggerated lens treatments
  • Interactive team pages with memorable portrait details

How it works

1 section

A three.js fragment shader remaps a small region of the photograph through an analytic lens while the rest of the image remains intact. Pointer and touch input position the lens, and GSAP animates its intensity and elastic return. The same source photograph provides a static fallback when motion or WebGL is unavailable.

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

Paid effect

Purchase to unlock the code.

Buying Funhouse Portrait 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. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

The synchronous probe prevents a flash of the source photo while the module loads. Keep the module error handler in step 3 so a failed network request restores the photograph.

2. Add to your <body>:

Code snippet omitted: it ships with the download.

3. Add before closing </body>:

Code snippet omitted: it ships with the download.

Already using three.js? Reuse your import and assign it to window.THREE before loading the effect script. Keep one three.js version per page. Use a local HTTP server; ES modules should not be opened through file://.

Using It With Your Own Design

The script needs a [data-funhouse] wrapper, a sized [data-funhouse-stage] button, and [data-funhouse-source] containing at least one image. Optional mode buttons and the next-photo button must be inside the wrapper. The script queries these data hooks, so the page shell, typography, colours and toolbar styling can all be replaced.

Keep the stage relatively positioned with a definite height, clipped overflow and touch-action: pan-y. Keep its canvas absolutely positioned, full-size and pointer-events: none. Source images use object-fit: cover, matching the shader's cover crop. Disable native image dragging. Hide the source under html.gl and .is-live, never simply because JavaScript is present; reduced motion must restore it. The supplied fallback hides motion-only controls until the root has data-ready="true".

Options

Set options on [data-funhouse] before initialisation.

Attribute Values Default Description
data-strength 0–1.2 1 Maximum held intensity; 0 disables deformation.
data-radius 0.1–0.5 0.30 Lens radius as a fraction of stage height.
data-drift 0 or 1 1 Gentle automatic lens drift on coarse pointers after three idle seconds. Set 0 for a completely still resting photograph.
data-funhouse-mode inflate, squeeze, twist Hook on a mode button. The initial mode is Inflate.
data-funhouse-next Presence Hook on the optional next-photo button.

Choosing Your Images

Choose a sharp, front-facing head-and-shoulders portrait with visible eyes, nose and mouth. Crop close enough that individual features are easy to target. A simple background keeps the lens readable; distant faces and soft-focus images lose the comic detail. The filter uses ordinary photographs and does not detect faces or require camera access.

Two portraits are included so you can try the filter on different facial features. The starting portrait is a generated studio image in a striped turtleneck; its master and generation prompt are included in assets/img-src/. Your replacement images stay in natural colour: this shader does not require a baked grade. The cover crop adapts to the stage ratio, so check the entire face remains accessible at your widest and narrowest layouts.

Accessibility

The portrait is a native button. Hold Space or Enter to apply the lens, use arrow keys to move it and release to settle. Tab reaches every mode and photo button, with visible focus outlines. Mouse and touch use the same press-and-drag interaction; vertical touch gestures remain available for scrolling.

Reduced motion shows the original photograph and hides the supplied motion controls. No JavaScript and unavailable WebGL use the same composed photo fallback. No device orientation permission or camera permission is needed.

Browser Support

Requires a browser with ES modules, import maps and WebGL for the interactive filter. Current Chromium, Firefox and Safari support these features. If WebGL is unavailable, the photo remains visible. Serve photos from the same origin or configure CORS on their host.

Performance

One image plane and one shader pass; no physics mesh or post-processing chain. Device pixel ratio is capped at 2 for fine pointers and 1.5 for coarse pointers. The loop pauses offscreen and when the document is hidden. To reduce GPU cost first, lower these caps in the renderer setup; radius and strength change appearance without reducing the number of shaded pixels. Use appropriately sized WebP photos to reduce loading cost.

Dependencies

Dependency Role
GSAP 3.12+ (demo uses 3.15.0) Press intensity, mode blending, elastic release, motion preference handling and teardown. No plugins.
three.js WebGL renderer, image textures and the analytic photo-lens shader; demo pins the ES module in the import map.

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.