Skip to content
animorio
IT
All components

The face button

Buttons, Transitions, HoverCanvas, GSAPAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

Sign in with Face ID on a black page: in the round end of the pill, inside the four corners of the Face ID frame, a face made only of contour lines, like the map of a hill. The lines come from a height map of a stylised face seen in 3D: the face follows the pointer and the contours slide over the cheeks and crowd on the side that turns away. At a click the face looks straight ahead, a scan line runs down it and the lines take its colour, then they tighten and gather into a tick: Signed in as Noor. Lock opens the tick back into the face.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The face button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hubot-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the face button: Sign in with Face ID. In the round end of the pill a face drawn only with contour lines, like a
         map: the lines are worked out from a height map of a face seen from where it is turned, so as it follows the
         pointer they slide and change. A click sends a scan line down it, the contours tighten and gather into a
         tick, and the pill opens on the person signed in -->
    <div class="fid" data-fid>
      <button class="fid__btn" type="button" data-btn data-autoclic>
        <span class="fid__fill" aria-hidden="true" data-fill></span>
        <canvas class="fid__sensor" aria-hidden="true" data-scan></canvas>
        <span class="fid__faces">
          <span class="fid__face" data-face="a">
            <span class="fid__word" data-wide="Sign in with Face ID" data-word>Sign in with Face ID</span>
          </span>
          <span class="fid__face" data-face="b" aria-hidden="true">
            <span class="fid__word" data-word></span>
          </span>
          <span class="fid__face fid__face--done" data-face="done" aria-hidden="true">
            <span class="fid__who">
              <span class="fid__lead" data-lead-text>Signed in as</span>
              <span class="fid__name" data-user-text>Noor</span>
            </span>
            <span class="fid__lock" data-lock-text>Lock</span>
          </span>
        </span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </div>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

24 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The face button” from Animorio and fit it into my site

What it does

On a black background a graphite pill 60 pixels tall, with a pale hairline all round. On the right, in white Hubot Sans, Sign in with Face ID; in the round left end the four rounded corners of the Face ID frame and inside them a face drawn only with seven blue-grey contour lines: the outline of the head, the loops of the cheeks, the two hollows of the eyes under the brow, the nose with its tip, the lips and the chin; the higher lines are paler. On arrival the button rises and the lines come in from the edge of the face to the tip of the nose. The face follows the pointer with a little weight: as it turns, the lines on the side moving away crowd and those on the other side open up; with no pointer it looks about slowly by itself. Under the mouse the words widen and the fill swells with a bounce. At a click the words become Signing in…, the face turns to look straight ahead and holds still, the corners close in a little and a mint line comes down the face with a fading trail: the contours above it turn mint. Then the contours tighten towards the middle and settle on a tick, the closed ones there and back; a clean mint tick remains, the corners go back to their place in mint and the pill widens on Signed in as and, under it, Noor, with a small Lock pill. A click on Lock: Locking…, the tick opens back into the face, the mint drains from the bottom up, Locked and then Sign in with Face ID again. With the keyboard the focus shows a white ring and Enter or Space sign in; on a phone the finger sliding over the page is where the face looks.

How it works

The height map of the face

The face is a sum of soft bumps on a low dome: a gaussian for the brow, two hollows for the eyes, a ridge and a tip for the nose with its wings, the cheeks, the lips with the line between them and the chin; outside the ellipse of the head there is no height. RELIEF says how many pixels deep the face is at the height of 1.

The contour lines

On a grid of half a pixel marching squares follows LINES heights, from the edge to the tip of the nose; the segments are chained into polylines through the cell edges they share (a closed ring comes back to its start), and smoothed twice. Every point of every line keeps its height, so the map can be turned in 3D.

The face looking at the pointer

Every frame each point of the lines is turned round the upright axis (yaw) and then the crosswise one (pitch), and projected with a light perspective (f = 110 / (110 − z)): the lines slide over the cheeks and crowd on the side moving away. The angle follows the pointer with the tanh of the distance, up to TURN radians, with an exponential chase that gives it weight. With no pointer two slow sines that never repeat make it look about (SWAY); off screen nothing is worked out.

The scan

The face turns back to the front in 0.35 seconds and holds still. The scan line comes down in SCAN seconds with sine.inOut, with a gradient band fading upwards behind it; the contours are drawn twice, clipped above and below the line: above in the scan's colour, below in grey. The corners of the frame close in by almost two pixels with back.out.

The contours tightening into the tick

When the scan ends the contours are frozen as they are. Each line knows the length fraction of every point: an open one is laid on the tick from its left end, a closed one starts from its left-most point and goes along the tick and back, becoming a thin double thread. First the line tightens towards the middle to half its size (power2.inOut), then it settles on the tick (power3.inOut), the outer ones first; at the end the lines fade and a clean tick with round ends remains.

The words and the states

Two faces for a line of words taking turns, plus the face of the person signed in; before every change the tweens still running on the faces are stopped, so two lines never stay on top of each other. It is a real button: aria-disabled while it scans and locks, an aria-live region says Signing in…, Signed in as Noor and Locked. Signed out.; once signed in the aria-label also says Lock. All simulated: no camera is switched on.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsSign in with Face IDThe words at rest. If they are too long for the room, the type gets smaller.
BUSYscript.jsSigning in…The words while it scans and the contours tighten into the tick.
LEADscript.jsSigned in asThe small line over the name.
USERscript.jsNoorThe person signed in. The sign-in is simulated: in a real site this is the name your server returns after WebAuthn.
LOCKscript.jsLockThe small pill that signs out.
LOCKINGscript.jsLocking…The words while the tick opens back into the face.
LOCKEDscript.jsLockedThe words once signed out, before going back to rest.
SCANscript.js1.1The seconds of the scan line.
LINESscript.js7How many contour lines, from the edge of the face to the tip of the nose: 4 a bare face, 10 a dense map.
TURNscript.js0.42How far the face turns towards the pointer, in radians: 0 it stays still.
RELIEFscript.js1How deep the face is: higher, the lines slide more as it turns.
SWAYscript.jstrueWith no pointer the face looks about slowly by itself; false it stays still, facing front.

Try them in the Playground

When to use it

For the sign-in button that uses the face or a passkey: a banking app, a password manager, a wallet, a members' area, the confirmation of a payment. It suits dark, technology and security sites. It has to be wired to the WebAuthn API, which asks the system for the face: here it is all simulated and the camera is never switched on.

On phones

The pill at rest is about 265 pixels wide and the open one about 215: at 390 pixels they fit with room to spare, and if the words are too long the type gets smaller. The finger sliding over the page is where the face looks; with no finger the face looks about by itself. A tap on the button scans.

With reduced motion

The face stays still, facing front, and does not follow the pointer; no lines coming in, no swelling fill or widening words (under the mouse the fill only lightens a little). At a click no line comes down: the contours fade to mint, then fade out and the tick fades in in their place; the pill takes its new width while the words are gone and Signed in as Noor fades in. Lock goes back the same way.

Pitfalls

  • The first contours were lines of equal depth seen from the camera: as the face turned, the depth took on a tilted plane (−x · sin yaw) that swamped everything, the contours became vertical bands and the face disappeared. Now the contours are those of the height map, worked out again every frame turned in 3D and in perspective: they stay a face and slide over the relief.
  • With a tall dome and nine levels the face, some thirty pixels wide, was a tangle that looked like a fingerprint. The dome is now low (0.56, with an exponent of 0.8), the nose and the eyes stronger and the lines seven: the head, the eyes, the nose and the mouth read.
  • The scan line started above the closed-in corners of the frame and cut through them. Now it runs from the edge of the head with half a pixel to spare, and the corners close in by 1.8 pixels instead of 2.6.
  • With the preview clicking every 2.8 seconds the sign-in was ready after 2.7 and the second click locked at once: Signed in as Noor showed for an instant. Now the sign-in is ready after about 3 seconds, the second click falls in the wait and the open pill stays in view for a whole round.

Libraries

GSAP 3.15.0, Hubot Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.

Testing

Tested on 12 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components