Skip to content

Components / Pointer

Liquid Image

Makes an image turn liquid under the cursor and settle back. A velocity field on the GPU (WebGL2 stable fluids, no dye) displaces where the picture is sampled, with a touch of chromatic split along the flow. The picture is a real <img>, which keeps alt, lazy loading, srcset and the LCP candidate, and is what shows without JavaScript or WebGL. The canvas above it takes over only while the fluid moves and hands back once it has settled. object-fit: cover and the image's object-position are matched in the shader. A cross-origin image without CORS, or one larger than the GPU takes as a texture (8192 px a side on many devices), stays a plain image. Every constant is a custom property re-read when the element's style changes.

Live demo

A poster: the word liquid over an orange disc in concentric rings

move your cursor across

Live, from the library itself: scroll, hover, navigate away and back. The demo is never gated.

Measured

JavaScript with its libs

13.1 kB raw

2.3 kB raw of its own, 1.3 kB gzip. The rest is the runtime, the canvas helper, the GL core and the fluid solver, loaded once for every effect on the page.

CSS 4.7 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Baseline · widely available

Chrome 56 · Firefox 51 · Safari 15; needs webgl2. Elsewhere: without WebGL2 or a colour-renderable float texture (EXT_color_buffer_float), for a cross-origin image served without CORS, or for an image past the GPU's texture size limit, the plain <img>.

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/liquid-image
src/pages/index.astro
<LiquidImage src="/poster.webp" alt="A poster: the word liquid over an orange disc in concentric rings" width={1440} height={720} class="aspect-[2/1] w-full" />

Without a key the registry answers 401 with an upgrade message. Related free primitives: image-trail, tilt.

Props

PropTypeDefaultDescription
srcstring | ImageMetadatanoneThe image: a URL, or an astro:assets import (its width and height are used). Its first load starts the GPU work; a later srcset candidate replaces the texture.
altstringnoneAlternative text, on the <img>.
width / height / srcset / sizes / fetchpriorityimg attributesnonePassed to the <img>.
loading'lazy' | 'eager''lazy'Loading of the <img>. Use eager (and fetchpriority="high") for an image above the fold.
strengthnumber1How far the picture moves with the fluid: velocity × a 40 ms look-back × strength.
chromanumber0.3Red and blue sampled apart along the flow, as a share of the displacement. 0 is none.
radiusnumber0.2Splat radius as a share of the box.
curlnumber6Vorticity confinement: how much the liquid swirls. 0 is laminar.
settlenumber1.5How fast the picture settles, per second. 3 is stiff, 0.8 lingers.
crossorigin'anonymous' | 'use-credentials'nonePassed to the <img>. A cross-origin image needs CORS to be uploaded to the GPU; without it the plain image stays.

Reduced motion

The <img> stays: the loop never runs and the canvas is hidden (by the media query, and by the page's own motion switch through the runtime).

With ClientRouter

Bound through the shared runtime: the loop, observers and pointer listener are released before the swap and the WebGL context is lost on purpose after it; five round trips leave one context. Under transition:persist the canvas keeps its context and starts again on the next page.

Why it is built this way

Replaces: LiquidImage / ImageTrail distortion (React Bits Pro) · WebGL image hover distortion (Codrops). See the migration table.

Small images pop up along the pointer's path and fade behind it.

every browserpointer

Tilt

Pointer

A card leans toward the cursor in 3D, lifts slightly, catches a glare, and settles back on the active preset's spring when the cursor leaves.

every browserpointerhover

Draws smoke that follows the cursor.

every browserpointeralways

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown