Build a glassy crystal button in Webflow that actually refracts the page behind it, then comes to life on hover — softening from frosted glass to crisp ice with a small nudge of GSAP. The trick isn’t a CSS blur; it’s a real SVG filter wired to the button through backdrop-filter, so whatever scrolls underneath bends and ripples through the glass.
What makes it worth the setup is how physical it feels. Because the filter reads the actual content behind the button, the effect changes as you scroll, and the hover animation gives it a tactile little lift. It looks like a lot of moving parts, but underneath it’s three SVG filter primitives, a few lines of JavaScript to feed it a texture, and a tiny GSAP timeline.
And none of it is locked to my class names. The script hooks onto an attribute, so you can name things however you like and still get the effect for free.
How it works
The glass look comes from an SVG filter, not a CSS blur. You paste the filter markup (from the cloneable) into an Embed element, give it display: none, and move it to the very top of the <body>. That placement matters: put the filter at the top so it’s global and always available — drop it lower and some browsers scope it locally or skip it, the same way a CSS variable belongs in :root. The filter carries id="glass", and the button references it with a custom property, backdrop-filter: url(#glass). That single line is the connection: the button grabs its filter source from the element with that ID.
Inside the filter, three primitives do the work. feImage loads the displacement map — a texture that acts like a topographic map, where lighter and darker areas tell the filter how far to push or pull each pixel — and outputs a result the rest of the chain can read. feGaussianBlur softens the button first; its stdDeviation controls how sharp or cloudy the glass looks (0.01–0.02 is a subtle haze, 1–2 is properly fuzzy). Then the star, feDisplacementMap, takes the blurred button and the map together and shifts the pixels — its scale is the intensity dial: 0 is no distortion, 1–5 gives gentle glass ripples, 20+ turns into funhouse-mirror chaos. The filter region is deliberately oversized (x="-1" y="-1" width="3" height="3") so nothing clips at the edges.
There’s one thing the markup alone can’t do: the displacement map is an external image, so a short JavaScript snippet fetches it and feeds it into feImage. Without that, the filter knows it should distort but has no texture to distort with. This script lives in a second Embed at the bottom of the body — not strictly required, since it runs after the DOM is ready, but it’s the cleaner, more predictable place for it. Crucially, the script targets the button by the fc-glass-button attribute, not by a class, so your naming convention is completely irrelevant.
The hover comes last, and it’s GSAP. A tiny timeline animates the displacement and blur from the resting “soft glass” state to a “crisp ice” state, nudging the button up and scaling it slightly for tactile feedback. You tune it with a handful of constants at the top of the code: hoverScale (the distortion strength — ~1.05–1.25 for a classy ripple, 1.40+ for jagged broken-crystal edges), hoverTransScale (the button’s scale transform — keep it modest), hoverBlur (0 for crisp, ~0.03 for a softer landing), duration (0.2s snappy, 0.4s elegant), and ease (e.g. power1.out, power2.out, expo.out). The same animation also runs on keyboard focus and reverses on blur, so keyboard users get the exact same experience — the effect stays accessible, not mouse-only.
How to use it
-
Clone the project. Grab the Webflow cloneable — it ships with the button, the SVG filter markup, the displacement-map loader, and the GSAP hover snippet, all commented.
-
Give the button contrast to refract. The effect only shines when there’s content behind it. In the demo the button sits
fixedand centered while a full-width background image scrolls underneath, so you can watch the filter change with the page. Any rich, scrollable background works. -
Style the glass base. Make the button background transparent with a subtle white linear gradient (roughly
135deg, white at ~20% → ~8% → ~3% opacity), a pillborder-radius, and a 1px white border at ~30% opacity. This frosted base is what the filter refracts — tweak or drop it freely; it changes the look, not the filter. -
Add the filter. Paste the filter markup into an Embed, set it to
display: none, and move that Embed to the top of the body. It hasid="glass". -
Link the filter to the button. On the button, add the custom property
backdrop-filterwith the valueurl(#glass). You’ll see a rough glass effect appear immediately. -
Load the displacement map. Paste the loader script into a second Embed (
display: none), placed at the bottom of the body. This feeds the map image into the filter so it has texture to bend. -
Tag the button. Add the attribute
fc-glass-buttonto your button so the script can find it regardless of its class name. -
Add the hover and turn on GSAP. Paste the GSAP snippet, then flip GSAP on in your project settings — the code does nothing until the library loads. Tweak
hoverScale,hoverBlur,duration, andeaseto taste. -
Reuse it. For several buttons on one page, duplicate the button — the loader already handles multiple instances. For other pages, turn the filter and script into components (edits then propagate everywhere), or place the script alone in the site-wide footer code and drop a button wherever you need it.