054

Liquid Fill Reveal

£10 or the whole Vault, £99

A scroll-scrubbed liquid fills a frame behind your content, with a simulated wave surface that sloshes on a fast scroll and inverts everything below the waterline.

ScrollTrigger Lenis advanced
7 more details
scrubpinningcanvasclip-pathvelocity-reactivephysicsfluid-surface

This demo reads better at your own screen size than in the frame below:

About this effect

A scroll-scrubbed liquid that fills a frame behind your own markup, with a surface that behaves like water rather than a moving line. Fill height follows scroll position. The wave on top follows scroll speed, so a slow read keeps the surface level while a hard flick tilts the whole body against the walls and sends it rolling back.

Read the full effect overview

Everything below the waterline switches to the submerged colour scheme and shears with the surface tilt, which is what sells the fluid as something the page is sitting in rather than a shape drawn over it. The boundary is a live clip-path built from the same samples the fluid is drawn from, so the colour changes exactly at the wave.

Your content stays real DOM throughout. Headings, links and buttons are yours to write and style; the effect copies them into an inert layer instead of redrawing them on canvas.

Liquid Fill Reveal - GSAP animation effect preview

What's included

8 items
  • Height-field water surface: 48 coupled spring columns trade height with their neighbours each frame, so an impulse at the wall travels across the tank and reflects
  • Scroll position drives the fill and scroll speed drives the wave, so one gesture produces two separate readings
  • Live clip-path polygon built from the same samples as the drawn fluid, which puts the colour boundary on the wave itself rather than near it
  • Refraction on the submerged layer: a capped sideways shift and shear taken from the surface tilt, measured in pixels so tall containers do not over-shear
  • Meniscus highlight rides the surface, and crests throw spray once the scroll is fast enough, on fine pointers only
  • Your markup stays real DOM. The submerged copy is inert and aria-hidden, so nothing is duplicated for assistive tech or the tab order
  • Damped settle into a slow idle ripple that never fully flattens, in place of a linear snap back
  • Coarse pointers get the same scroll-driven fill on a lighter 24 column surface with spray switched off

Perfect for

5 use cases
  • Swim, surf and watersport landing pages that need the water to actually do something
  • Drinks, brewing and bottling brands where the liquid is the product
  • Charity and campaign heroes about water, flooding or sea level
  • Spa, pool and wellness sites where the hero should feel like immersion
  • Agency and studio statement panels built around one memorable scroll moment

How it works

4 sections

One pinned, scrubbed ScrollTrigger drives a single value from 0 to 1 across roughly 1.8 viewport heights. That value sets the fill level directly, and its rate of change is read each frame as the impulse fed into the surface, which means the response already carries the scrub smoothing and behaves the same with or without Lenis.

The surface is an array of columns, each a damped spring pulled toward the rest level. A fixed 1/60s step integrates the springs, then two passes trade a fraction of each column's height with its neighbours, which is what turns an impulse into a travelling wave that reflects off the walls and interferes with itself. Scroll speed pushes one wall up and the other down, and a pair of off-centre kicks stops the two halves mirroring each other.

The fluid body, its meniscus and any spray are drawn on a canvas sized to the container with devicePixelRatio handling and re-derived on every ScrollTrigger refresh. The same column samples become a clip-path polygon on a cloned, inert copy of your content, so the submerged scheme starts precisely at the wave. The refraction shift and shear are applied to the copy's child rather than the clipped wrapper, which keeps the shear from dragging the waterline sideways with it.

A lighter surface runs on touch and a designed still under reduced motion, with no per-frame loop and no pin. The frame callback, both ScrollTriggers, the resize listener and the two injected layers are all removed in cleanup.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Liquid Fill Reveal opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add the scene to your <body>:

Code snippet omitted: it ships with the download.

3. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

The script finds every [data-liquid-fill] scene, inserts a canvas for the fluid, clones [data-liquid-content] into an inert copy stacked on top, and pins the scene for the configured scroll runway.

4. Write the submerged scheme:

The copy keeps every class from your original markup, so the below-water look is a set of overrides:

Code snippet omitted: it ships with the download.

Without these the submerged half stays in your normal colours, and ink type on a dark fill is unreadable. This step is not optional. Which direction you go depends on the fill: the demo is dark ink under a paper page, so the submerged scheme is the page inverted.

Using It With Your Own Design

What the effect needs from your markup: two attributes. data-liquid-fill marks the scene, and data-liquid-content marks the single element holding everything that should react to the waterline. If you omit the second one the script falls back to the scene's first element child. Nothing inside your content is ever inspected: it can be any markup at all, and the script copies the whole element rather than reading it.

The scene also needs a height. The demo uses height: 100svh, but any fixed or aspect-driven height works; a scene that sizes itself from its content is fine too, as long as it does not change height while pinned.

What is only the demo's styling: everything under DEMO: in style.css. The stage background, the panel layout, the type scale, the link, the scroll cue and the entire submerged palette are one worked example. Delete all of it and the effect still runs; the only thing you must replace is the .liquid-invert overrides described above.

CSS the effect depends on: the block marked EFFECT CONTRACT in style.css. Four rules matter and none of them are obvious:

  1. The scene is a single-cell grid. [data-liquid-fill] { display: grid } with [data-liquid-fill] > * { grid-area: 1 / 1 } puts the canvas, your content and the submerged copy in exactly the same box. That shared box is what makes the clip-path boundary land on the drawn wave instead of near it. overflow: hidden keeps the refraction shear inside the frame.
  2. Paint order is content, then canvas, then copy. The fluid is opaque and sits over your content, hiding the above-water version of anything that is currently submerged. Put the canvas underneath instead and every pixel of refraction shift leaves a pale ghost of the original along each edge. The canvas is pointer-events: none, so covering your content costs you no clicks.
  3. Pad the content, not the scene. Padding on [data-liquid-fill] insets the grid area, which insets the fluid too and leaves an unfilled border. Put your padding on [data-liquid-content].
  4. .liquid-invert is a grid as well, with its child at grid-area: 1 / 1. The clip-path goes on the wrapper, never on anything the refraction transforms. Put the clip and a transform on one element and shearing the content drags the waterline sideways with it. Inside the wrapper the shear sits on the cloned root and the per-element lean sits on each text element, so both stay clear of the clip.

Options

Every option goes on the [data-liquid-fill] element.

Attribute Values Default Description
data-liquid-fill marker required Identifies a liquid scene
data-liquid-content marker first child The element that is copied and clipped
data-liquid-color hex #3d5588 Fluid colour. The crest, body and floor tones are derived from it
data-liquid-from 0-1 0.08 Fill level at the start of the runway
data-liquid-to 0-1 0.86 Fill level at the end of the runway
data-liquid-rest 0-1 0.55 The level shown under reduced motion
data-liquid-runway viewport heights 1.8 Pinned scroll distance
data-liquid-scrub seconds 0.4 Scrub smoothing. Also sets how sharp a flick feels
data-liquid-columns integer 48 / 24 Surface samples. Defaults to 24 on coarse pointers
data-liquid-tension number 0.021 Spring pull back to level. Higher is tighter chop
data-liquid-damping 0-1 0.968 Velocity kept per step. Lower settles faster
data-liquid-impact multiplier 1 How hard scroll speed hits the walls
data-liquid-refract multiplier 1 Strength of the shift and shear below the line. 0 switches it off
data-liquid-droplets true, false true Spray off the crests. Always off on coarse pointers

Elements the Script Creates

Class Description
.liquid-canvas The fluid body, meniscus and spray. Inserted as the scene's first child
.liquid-invert The clipped copy of your content. Appended last, inert and aria-hidden

Style .liquid-invert and its descendants freely. Do not add a clip-path or a transform to either element in CSS; the script writes both every frame.

How It Works

One pinned, scrubbed ScrollTrigger drives a proxy value from 0 to 1 across the runway. That value sets the fill level directly. Its rate of change, sampled on GSAP's ticker, is the impulse fed into the surface, which is why the response already carries the scrub smoothing.

The surface is an array of columns, each a damped spring pulled toward the rest level. A fixed 1/60s step integrates the springs, then two passes trade a fraction of each column's height with its neighbours. That exchange is what turns a single impulse into a wave that travels across the tank, reflects off the far wall and interferes with itself. Scroll speed pushes one wall up and the other down; two off-centre kicks stop the halves mirroring each other, and a slow forcing term at the walls keeps a faint ripple alive once everything else has damped.

Each frame the same column samples do two jobs. They are drawn on the canvas as the fluid body, its meniscus and any spray, and they are written as a clip-path polygon on the copy of your content. Because both come from one array, the colour boundary is the wave rather than an approximation of it.

The refraction below the line is sized per element, not once for the whole copy. Each text element leans by a fraction of its own font-size, so a display headline gets a shift you can read as water while body copy in the same block moves a fraction of that. This matters more than it sounds: a single shift large enough to flatter a 90px headline is wider than a letterform at 16px, and it slides the two halves of every submerged word past each other until the text turns to mush. The shear on top is capped in pixels rather than in degrees, because a fixed angle throws a tall container much further sideways than a short one.

Tuning the Slosh

  • data-liquid-tension sets the frequency. Around 0.021 the surface rocks about once a second; 0.05 gives short chop, 0.008 a slow swell.
  • data-liquid-damping sets how long it lasts. Below about 0.94 the slosh is over before the eye registers it. Above 0.985 it rings for an uncomfortable length of time.
  • data-liquid-columns is a resolution control, not a quality one. Fewer columns give longer, smoother waves; more give finer detail and a shorter wavelength. Past about 64 the clip-path string gets long enough to show up in a frame profile.
  • data-liquid-scrub is worth touching last. It changes how the fill tracks the scroll, and because the impulse is read from that same value, it also changes how violent a flick feels.

Accessibility

  • Reduced motion: the gsap.matchMedia branch creates no pin, no ScrollTrigger and no ticker. It draws one still frame: fluid at data-liquid-rest, a level surface, the submerged scheme applied. The result is a designed composition, not a disabled one.
  • No JavaScript: neither the canvas nor the copy exists, and your content is displayed exactly as written. Nothing on the page depends on JavaScript to become visible.
  • Screen readers: the copy is inert and aria-hidden, and its id attributes are stripped, so the page still has one of everything and no duplicate targets.
  • Keyboard: the effect is scroll-driven and adds no controls. Because the copy is inert and the canvas is pointer-events: none, links and buttons in your content keep their focus behaviour and their clicks whether they are above or below the waterline.
  • Contrast: the fluid is opaque, so the submerged half needs its own palette. Check both schemes; the two ends of the fill gradient are different enough that text passing at the top and text sitting at the bottom should be measured separately.

Performance Notes

  • One canvas and one clip-path write per scene per frame. Both scale with column count, not with how much content you put in the scene.
  • The simulation runs on a fixed timestep with a four-step ceiling, so a stalled tab cannot spiral.
  • The ticker only runs while the scene is on screen, and stops once the settle has played out past the end of the runway.
  • Canvas resolution is capped at 2x device pixel ratio and re-measured on every ScrollTrigger refresh.

Dependencies

Required:

  • GSAP 3.12+
  • ScrollTrigger

Optional:

  • Lenis 1.x for smooth scrolling. If present, the script runs Lenis from GSAP's ticker so both systems share one clock.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.