GlassmorphismFree

Liquid Glass Refraction

A glass plate that genuinely bends the scene behind it: backdrop-filter chains an 8 px blur, 180% saturation and an SVG feDisplacementMap filter (fractal noise, up to 13 px displacement) that ripples over 22 s, above four moving color blobs; a highlight that follows the pointer, OKLCH tints and three fallbacks (no blur, reduced motion, reduced transparency).

SVG Filterbackdrop-filterOKLCH

Updated

Liquid Glass

Light refracts through the surface. Move your mouse.

Hover or click the scene to interact

This effect is free — the Glassmorphism category contains 47 effects total, including 6 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Marée Basse — live at Salle Mistral

Track 7 of 14
4K 47:12 / 1:32:08

① Playback controls over a filmed concert — an online concert platform

WhenThe viewer pauses a filmed concert: the controls appear in a glass plate laid over the stage image, LED wall and light beams included.
WhyThe refraction slightly bends the LED wall's dots under the plate: the controls stand out without a black bar eating the picture, and the highlight following the mouse shows that the plate is an object you handle.
Settings420 px plate at the bottom of the scene, reduced padding (14 × 18 px), title, progress bar, three real <button>s (previous, play, next) with play in aria-pressed; scenery: LED wall made of sharp 9 px dots (refraction shows on sharp edges), three sweeping beams in mix-blend-mode: screen, the crowd in silhouette.

Hautes-Combes regional nature park

Saut-Gris waterfall

Access
Free · car park 8 am – 8 pm
Trail
1.2 km · 25 min · 90 m climb

Swimming forbidden in the pool

Live · waterfall webcam

② Access times over the waterfall video — a regional nature park's website

WhenA natural site's page: the waterfall video plays in the background, the glass plate gives access, trail, best time and the swimming ban.
WhySeeing the streams of water bend behind the glass ties the plate to its subject; and the reduced-transparency fallback gives a solid background to those who ask for it, useful for times that must be read at a glance.
Settings232 px plate over the full height on the right, straddling the fall: streams of water as sharp stripes looping down (1.6 s), cut by a banded mask; list as a <dl>, warning on 55% red, a “Live” chip outside the glass; on phones (260 px high scene), the warning and the chip are hidden so the plate fits whole.

Le Phare · hotel bar · signature

Sea Breeze

  • Gin infused with sea fennel 4 cl
  • Elderflower liqueur 2 cl
  • Lime, blood orange 3 cl
  • Tonic, hand-cut ice top

Served from 6 pm16 €

③ A signature cocktail card — a hotel bar

WhenThe house cocktail's page on the hotel website: the coupe, its ice cubes and the lime wheel, the liquid plate with ingredients, measures and price.
WhyThe distortion evokes liquid and ice; the edges of the ice cubes and the bar's bottles ripple under the plate, and the text stays readable thanks to the text shadows built into the code.
Settings252 px plate on the right, straddling the coupe; a shelf of bottles as sharp color bands behind the plate; ingredients as a dotted list, measures right-aligned, price at 20 px.

How it works

Three layers in a 16/10 scene (.lgr-scene, 14 px corners, overflow: hidden, isolation: isolate, #0b1024 background). Underneath, .lgr-bg overflows by 10% and carries four pink, blue, green and violet blobs (46% wide, filter: blur(28px)) drifting on a 14 s loop, offset for each one. On top, the plate .lgr-glass — min(72%, 340px) wide padding included (box-sizing: border-box), 26 px corners — has a background in two near-white OKLCH tints at 20% then 7%, a white rim at 28%, a drop shadow and two inner highlights. The core is its line backdrop-filter: blur(var(--lgr-blur, 8px)) saturate(180%) url(#lgrRefract): the scene is blurred, saturated, then displaced pixel by pixel by the SVG filter shipped in the HTML.

The #lgrRefract filter builds fractal noise (feTurbulence, frequencies 0.009 and 0.013, two octaves, seed 7), softens it with a 1.4 blur, then uses it as a displacement map (feDisplacementMap, scale="26", red and green channels): each background pixel is shifted by up to 13 px according to the noise value. An <animate> tag swaps the two frequencies over 22 s, and the distortion ripples slowly like water. Over sharp stripes placed as background, the measurement in Chromium is unambiguous: the bands turn into waves under the plate. The highlight is a ::after radial gradient of 120 px, white at 45% in mix-blend-mode: screen, centered on --mx / --my; the script recomputes them on every pointermove over the plate (measured: pointer at 80% / 70% of the plate, --mx at 80%) and resets them to 30% / 20% when the pointer leaves.

Two choices in the code protect the page it is pasted into. Under prefers-reduced-motion, the script pauses the <svg> that holds the filter (disp.ownerSVGElement), not “the first <svg> on the page”: with an animated logo placed before the effect, the logo would stop and the distortion would keep going (measured). Next, Firefox and Safari ignore the whole backdrop-filter list as soon as it contains url(): measured with real screen captures of Firefox 153 and WebKit 26 over stripes, no blur, no saturation, no refraction — the plate would just be a white veil. A @supports (-moz-appearance: none) or (-webkit-backdrop-filter: blur(1px)) block, true only in those two engines (measured: false in Chromium), gives them the blur and saturation without the SVG filter; Chromium keeps the refraction. The script targets unique ids (#lgrScene, #lgrGlass, #lgrDisp): one plate per page as is. An older variant of this liquid glass also exists in the category.

Accessibility

  • Reduced motion: under prefers-reduced-motion, the blobs stop, the highlight no longer follows the pointer and drops to 50% opacity, and the filter's ripple is paused (measured: animationsPaused() true on the filter's <svg>, false on a neighboring animated logo). The plate stays refracted, but still.
  • Reduced transparency: @media (prefers-reduced-transparency: reduce) gives the plate a solid #14162a background and removes the blur and the highlight. Measured: the query responds in Chromium; Firefox 153 and WebKit 26 do not know this setting yet (neither reduce nor no-preference match), so the fallback does not apply there.
  • Contrast: the white text holds thanks to its drop shadows (black text-shadow at 35% on the heading, 55% on the paragraph). Without them, measured on the plate at six moments of the animation: 2.05:1 median and 1.45:1 on the lightest areas, when the green blob passes underneath. Keep those shadows, or darken the plate (first OKLCH tint at 0.4 lightness) if the text gets longer.
  • Semantics: the plate is a div with an h3 and a paragraph, the SVG filter is aria-hidden="true". The highlight only depends on the pointer: with keyboard and touch nothing is lost, it stays at its default place. If the plate holds controls, use real <button>s with a :focus-visible style that shows on the glass.

Browser compatibility

CSS + SVG + 1.2 KB of JavaScript, no dependency. Measured with real window screen captures (automated captures in Firefox and WebKit never show backdrop-filter, even on a control case): Chromium applies blur, saturation and SVG refraction; Firefox 153 and WebKit 26 apply blur and saturation thanks to the @supports block in the sold code, without the refraction — without that block they would apply nothing at all. The plate remains a readable frosted glass there, without the ripple. -webkit-backdrop-filter is declared for Safari before version 18. OKLCH colors need Chrome 111, Firefox 113, Safari 15.4; older, the plate background is ignored and only the rim remains.

Chrome 111+✓ Full (SVG refraction included)
Edge 111+✓ Full (SVG refraction included)
Firefox 113+✓ Blur and saturation, no refraction
Safari 15.4+✓ Blur and saturation, no refraction
Mobile iOS 15.4+✓ Blur and saturation, no refraction
Android Chrome 111+✓ Full (SVG refraction included)

With no backdrop-filter at all, the @supports not block gives the plate an 86% solid background (rgba(20, 22, 40, .86)): the text stays readable over the blobs. Without OKLCH, the plate's translucent background disappears but the rim and shadows remain. Without JavaScript, the highlight stays fixed at 30% / 20% of the plate and nothing else changes.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="lgr-scene" id="lgrScene">
    <div class="lgr-bg">
      <span class="lgr-blob lgr-blob--1"></span>
      <span class="lgr-blob lgr-blob--2"></span>
      <span class="lgr-blob lgr-blob--3"></span>
      <span class="lgr-blob lgr-blob--4"></span>
    </div>
    <div class="lgr-glass" id="lgrGlass">
      <h3>Liquid Glass</h3>
      <p>Light refracts through the surface. Move your mouse.</p>
    </div>
  </div>
  <svg width="0" height="0" aria-hidden="true" style="position:absolute">
    <filter id="lgrRefract" x="-20%" y="-20%" width="140%" height="140%">
      <feTurbulence type="fractalNoise" baseFrequency="0.009 0.013" numOctaves="2" seed="7" result="noise">
        <animate attributeName="baseFrequency" dur="22s" values="0.009 0.013; 0.013 0.009; 0.009 0.013" repeatCount="indefinite"/>
      </feTurbulence>
      <feGaussianBlur in="noise" stdDeviation="1.4" result="sturb"/>
      <feDisplacementMap id="lgrDisp" in="SourceGraphic" in2="sturb" scale="26" xChannelSelector="R" yChannelSelector="G"/>
    </filter>
  </svg>
CSS
.lgr-scene {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: 14px;
  overflow: hidden;
  isolation: isolate;
  display: grid;
  place-items: center;
  background: #0b1024;
}

.lgr-bg {
  position: absolute;
  inset: -10%;
  z-index: 0;
  filter: saturate(1.1);
}

.lgr-blob {
  position: absolute;
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(28px);
  opacity: 0.95;
  animation: lgr-float 14s ease-in-out infinite;
}

.lgr-blob--1 {
  background: #ff5d8f;
  top: -6%;
  left: 4%;
}

.lgr-blob--2 {
  background: #5d9bff;
  bottom: -8%;
  right: 2%;
  animation-delay: -4s;
}

.lgr-blob--3 {
  background: #3ad6b3;
  top: 28%;
  left: 38%;
  animation-delay: -8s;
}

.lgr-blob--4 {
  background: #c77dff;
  bottom: 10%;
  left: 10%;
  animation-delay: -11s;
}

.lgr-glass {
  position: relative;
  z-index: 2;
  box-sizing: border-box;
  width: min(72%, 340px);
  padding: 30px 28px;
  border-radius: 26px;
  color: #fff;
  background: linear-gradient(135deg, oklch(0.98 0.02 255 / 0.20), oklch(0.94 0.04 300 / 0.07));
  border: 1px solid rgba(255, 255, 255, 0.28);
  box-shadow:
    0 10px 40px rgba(0, 0, 0, 0.28),
    inset 0 1px 1px rgba(255, 255, 255, 0.55),
    inset 0 -2px 6px rgba(255, 255, 255, 0.10);
  -webkit-backdrop-filter: blur(var(--lgr-blur, 8px)) saturate(180%) url(#lgrRefract);
  backdrop-filter: blur(var(--lgr-blur, 8px)) saturate(180%) url(#lgrRefract);
  overflow: hidden;
}

.lgr-glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(120px 120px at var(--mx, 30%) var(--my, 20%), rgba(255, 255, 255, 0.45), transparent 60%);
  mix-blend-mode: screen;
  opacity: 0.8;
  transition: opacity .3s;
}

.lgr-glass h3 {
  margin: 0 0 6px;
  font-size: 19px;
  letter-spacing: -0.01em;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
}

.lgr-glass p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55);
}

/* Firefox et Safari ignorent TOUTE la liste backdrop-filter dès qu'elle contient url() (mesuré : ni flou ni
   saturation). Ces deux moteurs — repérés par une propriété qu'eux seuls connaissent — reçoivent le flou et la
   saturation sans la réfraction SVG, que seul Chromium applique. */
@supports (-moz-appearance: none) or (-webkit-backdrop-filter: blur(1px)) {
  .lgr-glass {
    -webkit-backdrop-filter: blur(var(--lgr-blur, 8px)) saturate(180%);
    backdrop-filter: blur(var(--lgr-blur, 8px)) saturate(180%);
  }
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lgr-glass {
    background: rgba(20, 22, 40, 0.86);
    border-color: rgba(255, 255, 255, 0.35);
  }
}

@media (prefers-reduced-motion: reduce) {
  .lgr-blob {
    animation: none;
  }

  .lgr-glass::after {
    opacity: 0.5;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .lgr-glass {
    background: #14162a;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-color: rgba(255, 255, 255, 0.4);
  }

  .lgr-glass::after {
    display: none;
  }
}

@keyframes lgr-float {

  0%,
  100% {
    transform: translate(0, 0) scale(1);
  }

  33% {
    transform: translate(12%, 8%) scale(1.12);
  }

  66% {
    transform: translate(-8%, -10%) scale(0.94);
  }
}
JavaScript (fx-0788)
(function() {
  /* ============================================================
     Sprint juillet 2026 — JS effets glassmorphism (5 IIFE scopées)
  ============================================================ */

  /* fx-0788 : Liquid Glass Refraction */
  (function() {
    var scene = document.getElementById('lgrScene');
    var glass = document.getElementById('lgrGlass');
    var disp = document.getElementById('lgrDisp');
    if (!scene || !glass) return;

    var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    glass.addEventListener('pointermove', function(e) {
      if (reduceMotion) return;
      var r = glass.getBoundingClientRect();
      glass.style.setProperty('--mx', ((e.clientX - r.left) / r.width * 100) + '%');
      glass.style.setProperty('--my', ((e.clientY - r.top) / r.height * 100) + '%');
    });
    glass.addEventListener('pointerleave', function() {
      glass.style.setProperty('--mx', '30%');
      glass.style.setProperty('--my', '20%');
    });

    if (reduceMotion) {
      try {
        /* la <svg> du filtre, pas la première <svg> de la page */
        disp.ownerSVGElement.pauseAnimations();
      } catch (e) {}
    }
  })();
})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Refraction strength (SVG — <feDisplacementMap scale="26">) 26 (shift of up to 13 px) 10 gives a barely rippled glass, 50 a bottle glass. Beyond 60, background text becomes unreadable under the plate and edges appear at the filter boundary (region from −20% to 140%).
Ripple size (SVG — <feTurbulence baseFrequency="0.009 0.013">) 0.009 × 0.013, two octaves, seed 7 The higher the frequency, the smaller the waves: 0.03 gives hammered glass. numOctaves="1" smooths the waves and lightens the computation. Change the <animate> values too, otherwise it overrides them.
Animated ripple (SVG — <animate dur="22s">) 22 s, back and forth between the two frequencies Remove the <animate> tag for a still glass, cheaper to display. dur="8s" gives choppy water.
Blur (CSS — --lgr-blur) 8px Set --lgr-blur: 14px on .lgr-glass for a stronger frost. The variable feeds both declarations, Chromium's and the @supports block's for Firefox and Safari.
Plate tint (CSS — background: linear-gradient(135deg, oklch(0.98 0.02 255 / .20), oklch(0.94 0.04 300 / .07))) bluish white at 20% to mauve white at 7% For a tinted plate: oklch(0.75 0.12 200 / .25) (turquoise). For long text: raise the first opacity to .35 or lower the lightness to 0.4.
Highlight (CSS — .lgr-glass::after) 120 px radial gradient, white at 45%, opacity .8 180px 180px for a wider highlight, opacity .5 for a discreet one. Rest position: the 30% / 20% values of pointerleave in the script.
Background behind the glass (HTML + CSS — .lgr-bg, .lgr-blob) 4 blurred blobs drifting over 14 s Replace them with an image or a video (object-fit: cover): refraction shows mostly on sharp edges, much less on blobs that are already blurred.

FAQ

Why doesn't the plate ripple in Firefox or Safari?

Because these two engines do not apply an SVG filter (url()) inside backdrop-filter, and they then reject the whole list, blur included — measured with real screen captures of Firefox 153 and WebKit 26 over a striped background: with url() in the list, no blur, the plate is just a white veil. The sold code gives them a separate block, detected by a property only they recognize (-moz-appearance for Firefox, -webkit-backdrop-filter for Safari), with the same blur and saturation without the filter. The day one of them applies url(), it will keep this frosted glass: the block breaks nothing, it only withholds the ripple.

Can I put several plates on the same page?

The CSS and the filter, yes: every .lgr-glass plate refracts with the same #lgrRefract, declared once. The script, no: it looks up #lgrScene and #lgrGlass by id and only drives the highlight of the first plate. For several plates, replace getElementById with a loop over document.querySelectorAll('.lgr-glass') and attach the same pointermove / pointerleave listeners to each — that is what the scenes on this page do.

The scene is cropped at the top and bottom with the full code, why?

It only happens if you fetch the full code with its demo frame (through the API or the MCP): the code copied from this page does not include that frame. The .demo-preview frame is 250 px high with overflow: hidden, and the scene takes its full width at 16/10. Measured at 784 px wide: the scene is 490 px tall, only its central 250 px band shows (the plate stays whole and centered); at 374 px the scene is 234 px tall and two 8 px gray bands appear above and below. In your page, remove the height of .demo-preview (or the whole frame) and give .lgr-scene the width you want: the 16/10 ratio then sets its height.