122

Reactive Face Mesh

£10 or the whole Vault, £99

Pull any part of a portrait out like taffy and the face reacts: eyes follow your hand, widen in surprise, blink as it snaps back.

advanced
8 more details
webgl-shadermesh-distortionelasticdrag-interactionpointer-trackingmorphinggenerated-photographytouch-gestures

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

About this effect

A portrait that notices you. Grab any part of the face and pull: the skin stretches out beyond the photograph in a tapering cone that stays glued to the pointer, and snaps back with an elastic twang when you let go.

Read the full effect overview

The face reacts the whole time. Her eyes follow the cursor around the page, glance at your hand as you start to pull, and widen in surprise while staring at it as the stretch grows; her mouth drops open, then grimaces and blinks as the skin snaps home. At rest she breathes, blinks and makes the small eye movements a real face never stops making.

Reactive Face Mesh - GSAP animation effect preview

What's included

8 items
  • Taffy stretch: any point of the photograph can be pulled far beyond its frame, with the grabbed skin fixed under the pointer and a tapering cone behind it
  • Elastic snap-back with overshoot, and a stretch that can be caught again mid-spring
  • Eyes that follow the cursor in nine directions, drifting smoothly near the face and jumping like real saccades further out
  • Surprise that looks where you are pulling: wide eyes aimed at the stretched tip, an open mouth, a grimace and blink on release
  • Optical-flow morphing between expression frames, so lids, brows and lips travel through every in-between instead of cross-fading
  • Idle life: blinks, fixation jitter, a slight head turn towards the pointer and slow breathing, each switchable off
  • Works with fewer frames: any expression not supplied falls back to its nearest relative, and missing motion maps fall back to a cross-fade
  • One WebGL draw per frame, rendered only while on screen, with a 2D canvas fallback and the plain photograph as the no-JS state

Perfect for

4 use cases
  • Playful About pages and team portraits that reward a visitor for touching them
  • Creative studio and agency heroes where the portrait is the joke
  • Campaign microsites, character mascots and interactive editorial covers
  • Portfolio pieces that need one memorable, tactile interaction

How it works

2 sections

The portrait is drawn as a WebGL triangle mesh. Each pull drags the skin under the pointer with it, the surrounding skin following less and less with distance, so a long pull tapers into a cone that lies over the face it passes; on release GSAP springs it home with an elastic overshoot.

Expressions are photographs of the same face that differ only inside an eye band or a mouth band, shipped as just those bands. For each one a small motion map records how every pixel moves from the neutral face. The fragment shader warps the neutral face part of the way along that map while pulling the expression back the rest of the way, so changes morph rather than dissolve; below the halfway point it is a pure warp, which is how the eyes drift after the cursor without ever showing two images. GSAP times every expression change, the reactions and the idle behaviour; the render loop runs on GSAP's ticker and only while the portrait is visible.

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.

Paid effect

Purchase to unlock the code.

Buying Reactive Face Mesh 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. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add the portrait anywhere in your <body>:

Code snippet omitted: it ships with the download.

The demo's index.html lists every expression the effect knows (21 of them); copy its attributes for the full set.

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

Code snippet omitted: it ships with the download.

Using It With Your Own Design

What the effect needs from your markup: an element with data-face-pull (the stage) containing one img.face-pull__img. The image's on-screen box is the photograph; the script lays a WebGL canvas over the whole stage, so a stretched feature can travel beyond the photo onto the stage around it. Expression frames and their motion maps are URLs in data-face-* attributes on the stage. Every instance on a page is independent.

What is only the demo's styling: the full-viewport stage, the grey ground, the "Grab her face and pull" pill (.face-cue) and the Mona Sans font. Size the stage to whatever space you want the stretch to be able to reach; the photo can be any size inside it.

CSS the effect depends on:

  • The stage must be a positioning context (position: relative); the canvas is position: absolute; inset: 0; pointer-events: none.
  • .is-live .face-pull__img { opacity: 0; touch-action: none; } hides the photo once the canvas has drawn it, while keeping it in place to catch touches. Until then (no JavaScript, a blocked CDN, no WebGL and no canvas) the plain photo shows.
  • Keep the image's aspect ratio: size it with max-width / max-height rather than forcing both dimensions.

Using your own portrait

The reactions are photographs of the same face, so a new face needs its own frames:

  1. A neutral portrait. Head-and-shoulders, straight on, eyes and brows clear of hair, plain background.
  2. Expression variants, made by editing only the eyes and brows (or only the mouth) of that exact photo, for example in an image editor or an AI image-editing tool. Paste each edit back onto the neutral photo through a soft-edged mask so that everything outside the band is pixel-identical. Any expression you skip falls back to its nearest relative (look-up-left to look-left, wide-right to wide) or is left out.
  3. Bands and motion maps. Run the included tool:

Code snippet omitted: it ships with the download.

It writes each expression as just its band plus a lossless flow-*.webp motion map. Use the same rectangles in data-face-band and data-face-mouth-band, and set data-face-eyes to the point between the pupils. Without motion maps the frames still work, but cross-fade instead of morphing.

Expression names: look-<dir> and wide-<dir> for each of left, right, up, down, up-left, up-right, down-left, down-right; plus wide, wince, blink, mouth-oh and mouth-grimace.

Options

Set these on the data-face-pull element.

Attribute Values Default Description
data-face-<name> URL none An expression frame: its band, or the whole photo
data-face-<name>-flow URL none That frame's motion map from tools/make-frames.py
data-face-band x y w h whole photo Eye/brow band in source-image pixels
data-face-mouth-band x y w h none Mouth band in source-image pixels
data-face-eyes x y 50% 40% of the photo Point between the pupils, in source-image pixels; gaze is aimed from here
data-face-radius 0.02 to 0.3 0.07 Grab radius as a fraction of the photo width
data-face-reach 0.1 to 1 0.6 Furthest pull as a fraction of the stage width
data-face-cell pixels 10 Mesh cell size; smaller is smoother and heavier
data-face-turn pixels 5 How far the head turns towards the pointer
data-face-intro true, false true One automatic cheek tug after load
data-face-idle true, false true Blinks, eye drift and breathing at rest
data-face-renderer webgl, 2d webgl Force the 2D canvas fallback (slower; no morphing or idle motion)

Accessibility

  • Keyboard: the stage is focusable; Left and Right arrows tug a cheek, Enter or Space tugs a random side. Give the stage a visible focus style.
  • Screen readers: the canvas is aria-hidden; the stage's aria-label and the image's alt text describe it.
  • Touch: a drag that starts on the face pulls it; elsewhere the page scrolls normally.
  • No JavaScript or no WebGL: the plain photograph is shown (the 2D canvas fallback still allows pulling where WebGL is missing).

Reduced Motion Behavior

When prefers-reduced-motion: reduce is set:

  • No intro tug, head turn, eye drift or breathing
  • Released features return with a short ease instead of an elastic overshoot
  • Pulling and the face's reactions still work, more gently

Dependencies

Required:

  • GSAP 3.12+ (the demo is pinned to 3.15.0). GSAP times every expression change, the reactions, the elastic snap-back and the idle behaviour, and runs the render loop on its ticker. The stretch and the morphing are drawn by WebGL.
  • A browser with WebGL 1 for the full effect

For your own portrait only:

  • Python 3 with opencv-python-headless and numpy, to run tools/make-frames.py

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