vaweGitHub

MovesReveal a title

Liquid displace

look: field2.6 sliquid-displace.md

Liquid displace: final frame of the text reveal animation
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. Clip: liquid-displace.mp4. Demo: demo/liquid-displace.html.

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 and timing

The clip runs 2.6 s. The look is field.

The clip uses 2 named eases from core/motion/presets.js. Each is a pair of After Effects handles: influence is how far along the segment the handle reaches, and speed is how fast the value moves at the key, as a multiple of the segment's average speed. Motion in the docs explains both.

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.