vaweGitHub

Video componentsReveal a title

Liquid displace

Reveal a titlefield2.6 s

liquid-displace.md

Liquid displace: final frame of the text reveal animation
00:00.0000:02.60
Tap the clip to play it.

When to use

Use when a title should arrive as if seen through moving water and then settle to perfectly sharp type: an SVG turbulence map bends the letters, and the bend drains to zero.

Use on one title per film. The type must hold readable and sharp for at least 1.2 s after it settles.

The code

html
<svg width="0" height="0" style="position:absolute"><filter id="liquid" x="-20%" y="-40%" width="140%" height="180%" color-interpolation-filters="sRGB">
  <feTurbulence id="noise" type="fractalNoise" baseFrequency="0.004 0.008" numOctaves="2" seed="7" result="n"/>
  <feGaussianBlur in="n" stdDeviation="8" result="s"/>
  <feDisplacementMap id="disp" in="SourceGraphic" in2="s" scale="0" xChannelSelector="R" yChannelSelector="G"/>
</filter></svg>
<div style="display:grid">
  <h1 id="title" style="grid-area:1/1;filter:url(#liquid)">Tidepool</h1>
  <h1 id="clean" style="grid-area:1/1;opacity:0" aria-hidden="true">Tidepool</h1>   <!-- the same title, no filter -->
</div>
<script type="module">
import '../../core/engine/page-api.js';
import { easeFn } from '../../core/motion/presets.js';
const vh = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--vh'));
vawe.onFrame((t) => {                                         // the seek time drives the filter, never a SMIL animate
  const left = 1 - easeFn('land')(Math.min(1, Math.max(0, (t - 0.05) / 1.1)));   // 1 at the start, 0 at 1.15 s
  const swap = Math.min(1, Math.max(0, (t - 0.5) / 0.16));    // the plain copy fades in over 0.5 to 0.66 s
  disp.setAttribute('scale', (vh * 0.3 * left ** 1.75).toFixed(2));
  noise.setAttribute('baseFrequency', `${(0.004 + 0.012 * left).toFixed(5)} ${(0.008 + 0.03 * left).toFixed(5)}`);
  clean.style.opacity = easeFn('swap')(swap).toFixed(3);
  title.style.opacity = swap >= 1 ? 0 : 1;
});
</script>

Easing

Each ease is a pair of After Effects handles from core/motion/presets.js. Every curve as CSS.

EaseLeaving a keyArriving at a key
land12% influence, speed 4.875% influence, speed 0
swap75% influence, speed 075% influence, speed 0

Notes

  • feDisplacementMap moves each pixel by scale times the noise channel minus one half, so the scale is the amount of bend. Tie it to the frame height so the bend holds at any size (0.3 of --vh here).
  • Drive the filter from vawe.onFrame, which gets the seek time. A live <animate> (SMIL) runs on the document clock, which the renderer does not seek, so every frame would come out the same.
  • baseFrequency falls with the scale, so the ripples widen as they calm. Wide ripples read as water; a high frequency that stays high reads as a glitch.
  • The notches come from the displacement map, which moves whole pixels with no smoothing. A bend of a pixel or two shifts a slanted edge in steps, and a high-frequency map makes the steps differ from one pixel to the next. Three defences: the noise is blurred (stdDeviation 8) and its y frequency is low, the scale falls on left ** 1.75 so it is under one pixel by 0.5 s, and an unfiltered copy of the title fades in over 0.5 to 0.66 s so the final frames are plain type, not a zero-scale resample.
  • Keep the filter region wide (x -20%, width 140%): a bent glyph at the edge of a tight region is cut.

Looks (ground, type, radius, accent) live in demo/demo.css; this snippet keeps neutral tokens.

Sound

Sound: none; a slow settle has no event to mark.

Chain it into a film.

Copy chains of video components from the recipes.