006
AI ready Refreshed

Liquid Morph Hover Effect

£7
or the whole Vault, £99

A GSAP liquid morph hover effect in WebGL: photographs melt into thick, glossy liquid that sags, smears and drips, then pull back together crisp.

advanced
4 more details
webgl-shadermorphinghover-effectscroll-reveal

About this effect

Photographs that melt like thick paint. Hover one and it sags under its own weight, smears along a slow flow field and runs into rounded drips past its bottom edge, while a glossy specular sheen picks out the liquid surface. Move away and it flows back up, sets, and springs into place as the original, perfectly crisp photograph.

Read the full effect overview

The demo is a small gallery: hover or tap any photograph, or press Melt everything to run the whole page at once and hold it until you let it set. Every image on a page shares one canvas and one WebGL context, and at rest nothing is drawn at all: the plain image is what shows, and it is also the fallback for no JavaScript, no WebGL and reduced motion.

Liquid Morph Hover Effect - GSAP animation effect preview

What's included

9 items
  • A real melt: gravity sag, rounded drips past the bottom edge and a slow sideways flow, not a noise wobble
  • Glossy liquid surface with a specular highlight, a reflected studio window and a rounded meniscus edge
  • The melt spreads out from where the pointer came in
  • Springy reform that lands on the exact, crisp original image
  • Hover, tap, focus, scroll-into-view, scroll-scrubbed and continuous triggers
  • data-liquid-trigger-all melts every image at once and mirrors its state in aria-pressed
  • Per-image intensity, duration, drip length and gloss, plus melt() and reform() from code
  • One shared canvas and WebGL context for any number of images; nothing renders at rest
  • The plain image is the fallback for no JavaScript, no WebGL and reduced motion

Perfect for

5 use cases
  • Portfolio and studio galleries where a hover should feel tactile and memorable
  • Fashion, beauty and product imagery that suits a glossy, liquid finish
  • Art, music and event pages with a playful, surreal tone
  • Editorial features where images arrive as liquid and set as you scroll
  • Landing pages that want one striking interaction on their hero imagery

How it works

2 sections

Every image stays a normal <img>. While one is melting, a shared canvas fixed over the viewport draws it in the same place, with room below for drips, and the <img> steps aside for exactly that time.

GSAP plays the melt and the springy reform, staggers the melt-all control and runs the frame loop on its ticker only while something is liquid; it also owns the reduced-motion branch, the optional ScrollTrigger modes and teardown. The shader turns that progress into the liquid and lights it, and at zero melt it draws the untouched photograph.

Difficulty Advanced
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.

The Vault opens this source and the whole library.

Each product gives you the HTML, CSS and JavaScript source, the full documentation and an AI setup prompt, licensed for 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.

One payment, over 300 products, and future additions to the library.

Documentation

Quick Start

1. Add to your HTML <head>:

In your downloadThe working code for this step, in index.html.

No head probe is needed. At rest the effect draws nothing: your <img> is what shows, and the canvas only takes over an image while it is melting, on exactly the same pixels.

2. Add the markup anywhere in your <body>:

In your downloadThe working code for this step, in index.html.

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

In your downloadThe working code for this step, in index.html.

If the module fails to load, the photographs simply stay as they are.

Why the import map rather than a plain <script src> for three.js: three ships as ES modules only, and its old UMD build logs a deprecation warning on every page load. The shim above imports it as a module (dynamically, so a blocked CDN leaves your images untouched instead of breaking the page), puts it on window, and then loads assets/script.js as an ordinary script, so the effect itself stays a plain file you can drop into any build (or none).

Already using three.js as a module? Then skip the shim, and just make sure window.THREE is set before assets/script.js runs:

In your downloadThe working code for this step, in script.js.

Choosing Your Images

The melt is made of the photograph, so what is in it decides how good it looks. The shader drags colour downward and sideways, stretches the bottom rows into drips and lights the result as a wet surface. So:

  • Hard edges and flat colour fields melt best. A facade against sky, a figure in a strong colour, flowers against a plain wall: you can see the edge bend, smear and run. A soft, low-contrast or busy-textured image has nothing for the eye to follow, and the melt reads as blur.
  • Mind the bottom edge. The drips are the bottom rows of the image pulled long, so a bottom edge with some colour in it makes richer drips than a flat white floor.
  • Any aspect ratio works. The shader cover-fits each image exactly like object-fit: cover, so the canvas lands on the same pixels as your <img>.

Judge an image fully melted (hover it and hold), not at rest.

Using It With Your Own Design

What the effect needs from your markup: an element with the class .liquid-morph that contains one <img>. The element is the frame: its size and its corner radius are what the liquid starts from and returns to. Each .liquid-morph is independent, and you can have as many as you like on a page; they all share one canvas and one WebGL context.

What is only the demo's styling: the toolbar, the three-column gallery, the captions, the font and the colours. Delete any of it. The melt-all button is optional; any element with data-liquid-trigger-all works.

CSS the effect depends on:

  • .liquid-morph.is-melting .liquid-morph__image { opacity: 0 }. While an element is liquid the canvas draws it, so the script adds .is-melting and the <img> steps aside for that time. The swap happens in the same frame the canvas starts and stops drawing, so it is invisible. Keep this rule.
  • .liquid-morph-canvas is position: fixed over the viewport with pointer-events: none and a z-index. The liquid is drawn there so drips can run past the frame, over whatever sits below it. Raise or lower the z-index to put it above or below your own fixed headers.
  • The <img> fills the frame with object-fit: cover. The shader matches that fit; contain or a fixed position would not line up at rest.
  • Leave some room below each image if you want the drips to run onto the page; they extend up to about 40% of the image height.

Options

Set on each .liquid-morph element.

Attribute Values Default Description
data-trigger hover, scroll, auto, none hover What melts it. hover also melts on tap (touch) and while a link or button inside it has focus. none is for driving it from code.
data-intensity 0 and up 1 How far the image runs. 0.5 is a gentle soften, 1.5 a full collapse.
data-duration seconds 1.3 Time to melt, and to reform before the springy settle.
data-drip 0 and up 1 Drip length. 0 sags and smears without dripping.
data-gloss 0 to 1 1 Strength of the specular sheen on the liquid.
data-hold seconds 0.9 How long a tap holds the melt before it reforms (touch).
data-interval seconds 2.4 Pause between melts for data-trigger="auto".
data-scrub true, false false scroll only: tie the melt to scroll position instead of playing it.
data-repeat true, false true scroll only: melt again when scrolled back above, so it re-forms on the next entry.

On any element on the page:

Attribute Description
data-liquid-trigger-all A control that melts every .liquid-morph at once and holds them; pressing again lets them set. The script mirrors the state in aria-pressed. Use a <button>.

How It Works

All the images on a page share one canvas fixed over the viewport and one WebGL context. While an image is melting, the script draws it on that canvas at the image's position, with room below for the drips; at rest nothing is drawn and your <img> shows.

The shader works out, for each pixel, which part of the photograph has flowed there: a sag under gravity that grows towards the bottom and runs into drips, a slow sideways flow, and a melt front that spreads out from where the pointer came in. The colour is averaged along the path it travelled, which pulls streaks through it, and the surface is lit as a glossy liquid with a rounded edge. At zero melt every one of those terms is zero, so the last frame of a reform is the untouched photograph.

GSAP plays the melt and the reform (the progress tweens, the elastic settle and the stagger of the melt-all control), runs the frame loop on its ticker only while something is liquid, and owns the matchMedia branches, the optional ScrollTrigger modes and teardown.

Accessibility

  • Reduced motion: nothing starts. Every photograph stays exactly as it is, and the melt-all control stays hidden.
  • Without JavaScript, or without WebGL: the same: the plain photographs. One fallback for every failure mode, and it is never hidden until the canvas is actually drawing in its place.
  • Keyboard: the melt-all control is a native <button> with aria-pressed. A .liquid-morph that wraps a link or button melts while that link has focus.
  • Screen readers: the canvas is aria-hidden; the <img> and its alt stay in the document throughout.
  • Touch: a tap melts the image and lets it reform; page scrolling is never blocked.

Browser Support

Anything with WebGL, which is every current browser. Support is probed before three.js is asked for a renderer: three logs its own failure to the console as errors before it throws, so catching the exception would hide nothing. Probed first, a visitor without WebGL simply gets the photographs and a clean console. Import maps are supported everywhere current; in a browser old enough to lack them window.THREE is never set and the photographs are again what shows.

Performance

One draw call per melting image, on one shared canvas; idle images cost nothing, and the loop stops completely once everything has set. Each image is uploaded to the GPU once when the page loads, not on the first hover. Images scrolled off screen are skipped, and data-trigger="auto" pauses while off screen. Pixel ratio is capped at 2 (1.5 on touch devices).

To buy back frames on low-end hardware, lower the pixel ratio cap in createStage() first: the cost is per pixel of liquid, and the smear takes six texture reads per pixel.

Demo Images

The three photographs ship bundled in assets/img/ as 800×1000 WebP derivatives (about 140 KB together), so the demo works offline and never hotlinks. They were chosen for the melt: a tiled facade against sky, a figure in yellow against sky and blue flowers against a pale wall, all hard edges and flat colour that show the smear, the drips and the reform clearly.

Replace a file in assets/img/ with your own, or point the src in index.html anywhere: the effect only needs an <img> inside .liquid-morph. No colour grade is baked in; the shader does the lighting. assets/img-manifest.json records the source of each image for provenance; the build script that reads it is part of the GSAP Vault repository and is not in your download.

Credits

Placeholder photographs from Pexels (photos 5504428 and 8746996, Pexels License) and Unsplash (photo 1515886657613-9f3515b0c78f, Unsplash License), used as cropped and re-encoded derivatives. Replace them with your own imagery before launch.

Dependencies

Dependency Version Required
three.js 0.180.0 Yes, as an ES module (see Quick Start)
GSAP core 3.15.0 Yes: plays the melt and reform, runs the frame loop on its ticker, matchMedia and teardown
ScrollTrigger 3.15.0 Only for data-trigger="scroll"

Everything the effect uses from three.js (WebGLRenderer, ShaderMaterial, PlaneGeometry, Texture) is long-stable API, so pinning a different version is a one-line change in the import map.

Also in your download: the full source, worked examples, the events and programmatic API, and the class reference.

£7