How to create a Liquid Glass effect with SVG filters

0
5


In June 2025 Apple revealed its new visual language, along with the beta launch of iOS 26. Liquid Glass was the name given to their “new and expressive material”, bending light, and distorting UIs. It received a mixed response, and just weeks after its launch we’ve seen Apple dial down their glassy effects to some degree.

liquid glassliquid glass

We’ve covered CSS glass effects on Tuts+ before, but today we’re going to do things a little differently. Let’s dive into SVG filters!

What is an SVG filter?

CSS provides us with the filter property that allows us to apply visual effects such as blur, brightness, contrast, saturation, and other effects to HTML elements. 

However, for more advanced, almost cinematic effects, SVG filters are a better choice. Unlike CSS filters, SVG filters are composed of modular building blocks known as primitives. When these primitives are combined, they produce powerful visual effects which would not be possible with CSS alone.

Some of the effects that can be applied using filters include custom blurs, water or ripple effects, realistic drop shadows, lighting effects, etc. 

SVG filters are created using the <filter> element whose syntax looks like this:

Just like any other  SVG element, the <filter> element accepts several attributes that control how it behaves and how much space it covers. These attributes determine factors such as position, size, and blur of the filter.

1
<svg width="200" height="200">
2
  <filter id="myFilter" x="0" y="0" width="100%" height="100%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse"/>
3
</svg>

  • id: used to identify the filter.
  • x , y are the positions of the filter relative to the target element.
  • width and height: specify the size to be covered by the filter.
  • filterUnits is the coordinate system for x, y, width, and height.
  • primitiveUnits defines the coordinate system inside the filter primitives

Inside the <filter> element is where you define the primitives. Primitives are also SVG elements and they include <feGaussianBlur>, <feOffset>, <feColorMatrix>, <feDisplacementMap>, etc.

In this tutorial, we’ll only focus on the <feGaussianBlur> and <feDisplacementMap> filters. These two are commonly used together to create effects such as glass distortion, ripples, and frosted backgrounds.

For example, to blur an element, we use the <feGaussianBlur> primitive which looks like this:

1
<feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur" />

The in attribute defines the input source of the filter primitive while the stDeviation determines the strength of the blur. Once we define the SVG filter, we’ll apply it to an HTML element or another SVG by referencing its id using the filter property.

In action

So for example, suppose we have a green SVG circle that looks like this:

1
<svg width="200" height="200">
2
  <circle cx="100" cy="100" r="80" fill="green" />
3
</svg>

If we needed to apply a blur to the SVG circle,  we would create a filter containing a <feGaussianBlur> primitive, and then apply the filter to the circle as shown below.

1
<svg width="200" height="200">
2
  <filter id="blur">
3
    <feGaussianBlur in="SourceGraphic" stdDeviation="20"/>
4
  </filter>
5
  <circle cx="100" cy="100" r="80" fill="green" filter="url(#blur)"/>
6
</svg>

SVG primitives typically have both an input(in) as well as an output(result). The output of one primitive is used as input of another primitive hence creating a chain of primitives for more advanced effects.

In this code snippet, in=sourceGraphic means that the blur will be applied to the original graphic, in this case, the green circle, while the stdDeviation attribute controls the strength of the blur. The higher the value, the more intense and spread out the blur effect becomes.

feImage Filter Element

The <feImage> filter primitive is used to bring an external image or SVG fragment into the filter chain. It does not apply any effects, rather it loads an image from a URL or base64 data. 

The output is then used by other primitives like the <feDisplacementMap> to create custom effects such as distortions, lighting, blending,  etc. 

For example, to load an image URL into the <feImage> filter primitive, we’ll have something like this:

1
<feImage
2
  href="https://example.com/image.png"
3
  x="0" y="0"
4
  width="100%" height="100%"
5
  result="map" />

feDisplacementMap Filter Element

The  <feDisplacementMap> filter primitive is responsible for creating distortion effects in SVG. It does this by distorting the color values using the pixels of a displacement map( loaded using the <feImage > filter) to shift pixels of the original graphic.

1
<feDisplacementMap
2
  id="disp"
3
  in="blur"
4
  in2="map"
5
  scale="0.8"
6
  xChannelSelector="R"
7
  yChannelSelector="G"
8
></feDisplacementMap>

For example, in the code above, the <feDisplacementMap> filter primitive takes 2 inputs where:

  • in="blur" is the output of the Gaussian blur
  • in2="map" is the displacement map image

The xChannelSelector attribute specifies which color channel from the displacement image is used to displace pixels along the x-axis while the yChannelSelector attribute specifies which color channel from the displacement image is used to displace pixels along the y-axis.

These channels can either be R(Red), G(Green) B(Blue) and A(Alpha). The scale attributes determine the intensity of the distortion effect.

Now that we know how SVG filters work and how to create them, we can create a liquid glass distortion effect and apply it to an HTML element.

Create a Liquid Glass button with SVG filters

We’ll start with the HTML markup, a simple button and a span element.

1
<button class="glass-button">
2
    <span>CRYSTAL</span>
3
</button>

Styling with CSS

To create a realistic glass button, we need three key ingredients:

  • A background blur to blur whatever is behind the button and create a translucent frosted look. We will achieve this using the feGaussian filter.
  • A background image will enhance the realism of the blur. 
  • Transparency; we need to make the button partially see-through. Without the semi-transparent background, the button would be solid, hence hiding the blur effect. 

Let’s add a background image to the page.

1
body {
2
  height: 150vh;
3
  background: url("https://images.unsplash.com/photo-1683657535824-5b570c7a1749?q=80&w=764&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D");
4
  background-size: cover;
5
}

We’ll also add a simple keyframe animation that will change the background’s position over time, hence creating a smooth floating animation.

1
body {
2
  height: 150vh;
3
  background: url("https://images.unsplash.com/photo-1683657535824-5b570c7a1749?q=80&w=764&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D");
4
  background-size: cover;
5
  animation: floatBG 15s ease-in-out infinite;
6
}
7

8
@keyframes floatBG {
9
  0%,
10
  100% {
11
    background-position: center center;
12
  }
13
  25% {
14
    background-position: 30% 70%;
15
  }
16
  50% {
17
    background-position: 70% 30%;
18
  }
19
  75% {
20
    background-position: 40% 60%;
21
  }
22
}

Add these styles to the button. 

1
.glass-button {
2
position: fixed;
3
top: 50%;
4
left: 50%;
5
transform: translate(-50%, -50%);
6
cursor: pointer;
7
outline: none;
8
width: 350px;
9
height: 180px;
10
border-radius: 999px;
11

12
}
13

14
.glass-button span {
15
font-family: "Orbitron", sans-serif;
16
color: white;
17
font-size: 2.5rem;
18
font-weight: 600;
19
text-transform: uppercase;
20
letter-spacing: 0.1em;
21

22
}

We have positioned the button at the center using fixed:position so it stays in place when the background animates. This will ensure the glass effect is visually enhanced.

By default, the button has a solid background, we’ll replace it with a transparent gradient and a semi-transparent border to also enhance the glass illusion.

1
.glass-button {
2
  position: fixed;
3
  top: 50%;
4
  left: 50%;
5
  transform: translate(-50%, -50%);
6
  cursor: pointer;
7
  outline: none;
8
  width: 350px;
9
  height: 180px;
10
  border-radius: 999px;
11
  border: 1px solid rgba(255, 255, 255, 0.3);
12
  background: linear-gradient(
13
    135deg,
14
    rgba(255, 255, 255, 0.2) 0%,
15
    rgba(255, 255, 255, 0.08) 50%,
16
    rgba(255, 255, 255, 0.03) 100%
17
  );
18
}

So far, the button looks like this:

We need to make it more glassy by adding SVG filters. 

Glass distortion 

To create the glass distortion effect with SVGs, we’ll use a combination of blur, displacement, and a texture map. Let’s start by defining the <filter> element where our primitives will be defined. 

1
<svg style="position: absolute; width: 0; height: 0;">
2
  <filter id="glassFilter">
3
  </filter>
4
</svg>

Next, we’ll add a Gaussian blur to the source graphic using the <feGaussianBlur> primitive. We’ll also ensure it’s available for use in the next chaining process by storing its output in the result attribute. 

1
<feGaussianBlur in="SourceGraphic" stdDeviation="0.02" result="blur" />

A good displacement map image should have high contrast and noticeable color variations. This is important because the distortion depends on the intensity of the color channels.

To ensure the displacement image covers the whole filter area, we have defined x,y, width, and height. Depending on the size of the element the filter will be applied to, you will need to adjust these values to achieve the desired result.

1
<feImage x="-50%" y="-50%" width="200%" height="200%" result="map"/>

As you can see in the <feImage> filter code above, we haven’t set the href directly because the base64-encoded version of the image would be very long. Instead, we’ll use JavaScript to set the href dynamically.

Add this code in your JavaScript file 

1
const feImage = document.querySelector('feImage');
2
fetch('https://essykings.github.io/JavaScript/map.png')
3
    .then((response) => {
4
        return response.blob();
5
    })
6
    .then((blob) => {
7
        const objURL = URL.createObjectURL(blob);
8

9
        feImage.setAttribute('href', objURL);
10
    });

Here, we first select the <feImage> element using querySelector, then we fetch the image from a hosted URL, convert the response into a Blob (a binary representation of the image), and then assign the objectURL as the href value of the <feImage>.

We now have the blurred content and the image map, so let’s combine them using the <feDisplacmentMap> filter to get the final glass distortion effect which will be added to the button.

1
<feDisplacementMap id="disp" in="blur" in2="map" scale="0.8" xChannelSelector="R" yChannelSelector="G"/>

Now, the final SVG filter looks like this:

1
<svg style="position: absolute; width: 0; height: 0">
2
  <filter id="glass" x="-50%" y="-50%" width="200%" height="200%" primitiveUnits="objectBoundingBox">
3
    <feImage x="-50%" y="-50%" width="200%" height="200%" result="map"/>
4
    <feGaussianBlur in="SourceGraphic" stdDeviation="0.02" result="blur"/>
5
    <feDisplacementMap id="disp" in="blur" in2="map" scale="0.8" xChannelSelector="R" yChannelSelector="G"/>
6
  </filter>
7
</svg>

Apply the SVG glass distortion effect on the button using the backdrop-filter property.

1
backdrop-filter: url(#glass);

Finally, to enhance the interactivity of the glass button, let’s add a zoom effect on hover. 

1
.glass-button:hover {
2
  transform: translate(-50%, -50%) scale(1.05);
3
}

Final result

Here is the final result of the glass button in action:

Conclusion

We have seen how powerful SVG filters can be, with just a few SVG filter primitives, we have created a  realistic glass distortion effect, similar to Apple’s Liquid Glass effect. Now you can use SVG filters to create visual styles directly in your browser. 



Source link