How the moving ink in the header is drawn
There are no images in the header canvas. Everything is computed live: two triangles cover the screen, and every pixel runs the same fragment shader.
Palette
Once the colors are set, half the picture is done. I picked three from traditional Chinese painting pigments:
| Name | RGB | Role |
|---|---|---|
| Ink (墨) | (.071, .071, .078) |
Base |
| Rice paper (宣纸) | (.925, .894, .827) |
Highlights |
| Azurite (石青) | (.165, .353, .545) |
Midtones |
The whole image is an interpolation between these three.
Noise and domain warping
The building block is a 3D noise function, noise(vec3), stacked five octaves deep into fbm (fractal Brownian motion):
float fbm(vec3 p) {
float sum = 0., amp = 1., norm = 0.;
for (int i = 0; i < 5; i++) {
sum += amp * noise(p);
norm += amp;
amp *= 0.5;
p = p * 2.03 + vec3(3.1, 1.7, 0.);
}
return sum / norm;
}
Using fbm directly as color looks like clouds, not ink. The key step is domain warping: two fbm calls produce an offset field q, which is added back onto the coordinates, and the warped coordinates are used to sample the noise a second time.
vec2 q = vec2(fbm(vec3(p * 0.5, t * 0.1)),
fbm(vec3(p * 0.5 + vec2(1.0), t * 0.1)));
vec2 pw = p + 3. * q;
float n = fbm(vec3(pw, t * .07));
The 3. * q factor controls how far the ink bleeds. Turn it up and it gets wilder; turn it down and it looks more like fog.
Mapping to color
The noise is squeezed into 0–1, then two smoothstep ranges control the ink → azurite and azurite → paper transitions:
float k = clamp(0.5 + 2. * n, 0., 1.);
vec3 col = mix(INK, QING, smoothstep(.1, .5, k));
col = mix(col, PAPER, smoothstep(.5, .9, k));
Time t slowly advances along the noise’s third dimension, so the image spreads like ink in water.
What changed when it became a header
The assignment canvas was square. The header is a wide strip, which would stretch the noise horizontally, so a uAspect uniform scales the x coordinate by the aspect ratio to keep the noise isotropic. Also:
requestAnimationFramereplacessetInterval, still capped at 30 fps;- drawing stops when the header scrolls out of view or the tab goes to the background;
- with “reduce motion” turned on in the OS, a single still frame is drawn;
- device pixel ratio is capped at 1.5 and canvas width at 1920 to save GPU.