
Ink that reveals
WebGL, Hover
Free
A picture printed in grey. The pointer stirs a fluid that brings the colours back where it passes; the colour stays much longer on near things, which also take it sooner, and the picture’s three layers shift a little with the pointer.
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>Colour that clings</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/400.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/600.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="bar">
<a class="brand" href="#">Highfield Balloon Week</a>
<nav class="bar__nav" aria-label="Sections">
<a href="#">Programme</a>
<a href="#">Flights</a>
<a href="#">Visit</a>
</nav>
</header>
<!-- the picture: three layers drawn by the script (sky, hills, the big balloon), grey until the pointer stirs colour in -->
<main class="stage" data-stage>
<canvas class="stage__canvas" data-canvas aria-hidden="true"></canvas>
<div class="stage__copy">
<p class="kicker">14–17 September · Highfield Downs</p>
<h1 class="title">Up before <em>the sun.</em></h1>
<p class="note" data-hint>Run the pointer over the picture: the colour follows it, and stays longest on what is closest.</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>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.
26 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “Colour that clings” from Animorio and fit it into my siteThe script draws the picture on three canvases, the sky with the far balloons, the hills with the halfway ones, the big balloon with the grass, and once more all together in greys for the depth: white near, black far. The four canvases become WebGL textures; at a new screen size they are drawn again.
It is a fluid in WebGL2 on a grid of GRID cells per pixel, at half precision. The brush pours velocity (FORCE for every pixel of movement) and dye (DYE per touch), in several touches along fast strokes; then vorticity (CURL), divergence, ITERATIONS steps of pressure, the gradient and the advection. Velocity and dye keep VELOCITY_FADE and TRAIL_FADE every sixtieth of a second.
Depth changes the fluid in three places. On near things the dye fades much less: its factor moves towards 0.998 in proportion to depth times CLING. Near things take colour sooner: the threshold is THRESHOLD times (1 minus BIAS times the depth). And they push the fluid aside: BLOCK times the depth is added to the pressure.
The shader reads the three layers shifted in proportion to their depth (PARALLAX times the pointer’s position, followed with a lag), stacks them, and shows them in grey, the luminance times GREY, or in colour where the fluid’s dye passes the threshold, with an edge (1 minus EDGE) times 0.35 wide. A slight zoom keeps the edges hidden when the layers shift.
After the last move the fluid runs until the colour on the nearest things is gone, about 14 seconds with these values; then the fields are cleared and the frames stop until the pointer comes back.
| Name | File | Default | What it changes |
|---|---|---|---|
BRUSH | script.js | 2.8 | The size of the brush, in thousandths of the picture’s height squared. |
FORCE | script.js | 3 | How hard the pointer pushes the fluid, for every pixel it moves. |
DYE | script.js | 0.65 | How much colour every touch of the brush pours in. |
CURL | script.js | 4 | How much the fluid curls (0 = no swirls). |
VELOCITY_FADE | script.js | 0.906 | How much speed is left after every sixtieth of a second. |
TRAIL_FADE | script.js | 0.947 | How much colour is left after every sixtieth of a second on the far things. |
CLING | script.js | 0.9 | How hard the colour holds on to near things: 0 = as on far ones, 1 = it hardly fades. |
BLOCK | script.js | 0.33 | How much the near things push the fluid aside. |
BIAS | script.js | 0.9 | How much sooner the near things take their colour. |
THRESHOLD | script.js | 0.66 | How much dye a far thing needs before its colour shows. |
EDGE | script.js | 0.97 | How hard the edge of the colour is (0 = soft, 1 = hard). |
GREY | script.js | 0.92 | The brightness of the grey print. |
PARALLAX | script.js | 0.015 | How far the layers shift with the pointer. |
ITERATIONS | script.js | 12 | The steps of the pressure: more steps, a more precise fluid. |
GRID | script.js | 0.4 | The cells of the fluid for every pixel of the screen. |
For a picture that should invite a touch: a place, a product, a person at the opening. It works best with a scene that has layers: the near thing keeps its colour, the background loses it quickly.
Without a pointer you paint with a finger: on the picture the finger does not scroll the page. The layers shift following the finger, the brush is the same.
No fluid and no shifting layers: the picture is there at once, still and in colour.
GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Barlow 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 2 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.