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.