Table of Contents
Step 0: Patience and space
Step 1: Get the basic image in place
Step 2: Distort the image
Step 3: Clip the text
Step 4: Reveal the full image
Step 5: Place the text… again
Step 6: Creating the dark edge of the text
Step 7: Let’s do the light edge
Step 8: Combine the edges
Step 9: Yes, a bevel
Step 10: All together now!
Home Web Front-end CSS Tutorial Making a Realistic Glass Effect with SVG

Making a Realistic Glass Effect with SVG

Apr 18, 2025 am 10:01 AM

Making a Realistic Glass Effect with SVG

I’m in love with SVG. Sure, the code can look dense and difficult at first, but you’ll see the beauty in the results when you get to know it. The bonus is that those results are in code, so it can be hooked up to a CMS. Your designers can rest easy knowing they don’t have to reproduce an effect for every article or product on your site.

Today I would like to show you how I came up with this glass text effect.

Step 0: Patience and space

SVG can be a lot to take on, especially when you’re just starting to learn it (and if you are, Chris’ book is a good place to start). It’s practically a whole new language and, especially for people who lack design chops, there are lots of new techniques and considerations to know about. Like HTML, though, you’ll find there are a handful of tools that we can reach for to help make SVG much easier to grasp., so be patient and keep trying!

Also, give yourself space. Literally. SVG code is dense so I like to use two or three new lines to space things out. It makes the code easier to read and helps me see how different pieces are separated with less visual distraction. Oh, and use comments to mark where you are in the document, too. That can help organize your thoughts and document your findings.

I’ve made demos for each step we’re going to cover in the process of learning this glass effect as a way to help solidify the things we’re covering as we go.

OK, now that we’re mentally prepared, let’s get into the meat of it!

Step 1: Get the basic image in place

First things first: we need an image as the backdrop for our glass effect. Here we have an element and an within it. This is similar to adding an Making a Realistic Glass Effect with SVG in HTML. You’ll notice the dimensions of the viewBox attribute and element in the SVG element are the same. This ensures that the is exactly the same size as the actual picture we’re linking to.

That’s a key distinction to note: we’re linking to an image. The SVG file itself does not draw a raster image, but we can reference one in the SVG code and make sure that asset is in the location we point to. If you’ve worked with Adobe InDesign before, it’s a lot like linking to an image asset in a layout — the image is in the InDesign layout, but the asset itself actually lives somewhere else.

Step 2: Distort the image

Straightforward so far, but this is where things get complicated because we’re going to add a filter to the image we just inserted. This filter is going to distort the image. If you look closely at the difference between the demo in the last step and the one in this step, you’ll see that the edges of objects in the image are a little rough and wavy. That’s the filter at work!

First, we create another to hold filter. This means that if we ever want to reuse our filter — for example on multiple elements on the page — then we totally can!

Our first filter (#displacement) is going to distort our image. We’re going to use feTurbulence and feDisplacementMap, each explained by Sara Soueidan much better than I can in this post. Beau Jackson also wrote up a nice piece that shows how they can be used to make a cloud effect. Suffice to say, these two filters tend to go together and I like to think of them as when something needs to appear “wobbly.”

With our filter container in place, we just need to apply that filter to our image with a filter attribute on the , magic!

<svg>

  <!-- more stuff -->
  
  <!-- DISTORTION IMAGE: clipped -->
  <image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5946/kyoto.jpg" width="1890" x="0" height="1260" y="0" clip-path="url(#clip)" filter="url(#distortion)"></image>
  
  <!-- FILTER: wobbly effect -->
  <filter>
    <feturbulence type="turbulence" basefrequency="0.05" numoctaves="2" result="turbulence"></feturbulence>
    <fedisplacementmap in2="turbulence" in="SourceGraphic" scale="20" xchannelselector="R" ychannelselector="G"></fedisplacementmap>
  </filter>

  <!-- more stuff -->

</svg>
Copy after login

Step 3: Clip the text

We don’t want the entire image to be distorted though. We’re going to clip the shape of our distorted to the shape of some text. This will essentially be the portion of the picture seen “through” the glass.

To do this, we need to add a element in a and give it an id. Calling this id in the clip-path of our now restricts its shape to that of our . Wonderful!

Step 4: Reveal the full image

OK, so it’s bueno that we have the distorted clipped to the , but now the rest of the image is gone. No bueno.

We can counteract this by adding a copy of the same but without the clip-path or filter attributes before our existing . This is where I like to add some nice comments to keep things neat. The idea is like placing a transparent layer over what we have so far.

I know, I know, this isn’t very neat, and we’re repeating ourselves. Ideally, we would set our filter straight on the element and use the in="BackgroundImage property for feDisplacementMap to warp what’s behind the text, without the need for extra elements. Unfortunately, this has poor browser support, so we’re going to go with multiple images.

<svg>

  <!-- more stuff -->

  <!-- BACKGROUND IMAGE - visible -->
  <image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5946/kyoto.jpg" width="1890" x="0" height="1260" y="0"></image>
          
  <!-- DISTORTION IMAGE - clipped -->
  <image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5946/kyoto.jpg" width="1890" x="0" height="1260" y="0" clip-path="url(#clip)" filter="url(#distortion)"></image>

  <!-- more stuff -->

</svg>
Copy after login

Step 5: Place the text… again

Next, we’re going to duplicate our text just as we did for the image in the last step. Unfortunately, because the text is in a clip-path, it’s now not available for rendering. This is the last time we’re going to duplicate content like this, I promise!

Now we should have something that looks like a normal image with black text over it. If the distortion filter on the we’ve already made is what we can see “through” the glass, then our new is going to be the glass itself.

<svg>

  <!-- more stuff -->

  <!-- TEXT - clipped -->
  <clippath>
    <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">KYOTO</text>
  </clippath>
        
  <!-- TEXT - visible -->
  <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">KYOTO</text>

<!-- more stuff -->

</svg>
Copy after login

Step 6: Creating the dark edge of the text

This is where things start to get exciting, at least for me! ?

We want to create a dark edge along the text element which, when paired with a light edge (we’ll look at that next), will add depth to the appearance of the text against the image.

We want a new filter for our , so let’s create one in our filter’s SVG element and give it an 4" to achieve the glass effect, but see what happens if you set it to 1… or 100!

feOffset is used to move all the “pixels” in the previous primitive ( feMorphology ) across the x- or y-axis. The values dx="5" and dy="5" move the “pixels” right on the x-axis and y-axis, respectively. The higher the number, the further they move. Put in negative numbers for dx and the “pixels” will move left. Negative dy and they’ll move up! Again, the is the sort of thing you start to learn as you play around with them.

The reason I have quotes around “pixels” is because they’re not screen pixels like you might expect in CSS. Rather, they refer to the dimensions we set on the parent . I think of them as percentages. We have used these settings viewBox="0 0 1890 1260" in our example. This means our is 1890 “pixels” wide. If we set dx="189" it means we’ll move our element 10% of the way across the SVG (1890 divided by 189).

feFlood is great. If you want to fill the screen with color, this is the primitive you need! You might wonder why we can’t read our text now when we apply it. That’s because you can only see the result of the last filter primitive that was created. The result of each of the previous primitives was related to our element. The result of feFlood is just like its name: a flood of color. It doesn’t know what you did before and it doesn’t care — it’s merely going to fill an area with color.

This is where some people start getting frustrated with SVG. It’s hard to work on something when you can’t see it! Trust me, as you work with SVG more you’ll get used to this. In fact, the next few steps will need us to rely on this and trust that everything is still in place.

feComposite is going to solve this issue for us. What does it do? MDN describes it as:

The SVG filter primitive performs the combination of two input images pixel-wise in image space using one of the Porter-Duff compositing operations: over, in, atop, out, xor, and lighter.

That to me is jibba-jabba. I think of it as affecting the alpha layer of in with the color/alpha of in2.

With this in place we can once again see our text spelled out and, because the color we used is slightly transparent, we can even see the distorted “glass” effect coming through. Great!

<svg>

  <!-- more stuff -->
    
  <!-- dark edge -->
  <femorphology operator="dilate" radius="4" in="SourceAlpha" result="dark_edge_01"></femorphology>
    <feconvolvematrix order="3,3" kernelmatrix="1 0 0 
      0 1 0
      0 0 1" in="dark_edge_01" result="dark_edge_02"></feconvolvematrix>
    <feoffset dx="5" dy="5" in="dark_edge_02" result="dark_edge_03"></feoffset>
    <feflood flood-color="rgba(0,0,0,.5)" result="dark_edge_04"></feflood>
    <fecomposite in="dark_edge_04" in2="dark_edge_03" operator="in" result="dark_edge"></fecomposite>
    
  

  <!-- more stuff -->

</svg>
Copy after login

Step 7: Let’s do the light edge

This is essentially the same as what we literally just did, but we’re going to shift the shape up and to the left using negative dx/dy values. We’re also setting a slightly white color this time. We’re aiming for a nice depth effect.

We’re again in a position where what we can see is the most recent result from a filter primitive, but we can’t see our dark edge! feComposite isn’t what we want to use to bring them together because we don’t want the alpha of the dark edge colored by the light edge… we want to see both! Which leads us to…

<svg>
  <filter>

    <!-- more stuff -->

      <femorphology operator="dilate" radius="4" in="SourceAlpha" result="light_edge_01"></femorphology>
      <feconvolvematrix order="3,3" kernelmatrix="1 0 0 
        0 1 0
        0 0 1" in="light_edge_01" result="light_edge_02"></feconvolvematrix>
      <feoffset dx="-2" dy="-2" in="light_edge_02" result="light_edge_03"></feoffset>
      <feflood flood-color="rgba(255,255,255,.5)" result="light_edge_04"></feflood>
      <fecomposite in="light_edge_04" in2="light_edge_03" operator="in" result="light_edge"></fecomposite>

    <!-- more stuff -->
  
  </filter>
</svg>
Copy after login

Step 8: Combine the edges

feMerge! It’s a hero. It lets us take any number of primitive results and merge them, making a new image. Woohoo, we can now see both dark and light edges together!

However, we do want them to be edges rather than both filling up the entire text, so we need to remove the space that the original takes up. What we need next is another feComposite to chop out the original SourceGraphic. Because we used feMorphology to fatten the letters for our edges, we can now chop the original letter shapes out of the result of our feMerge.

<svg>
  <filter>

    <!-- more stuff -->

    <femerge result="edges">
      <femergenode in="dark_edge"></femergenode>
      <femergenode in="light_edge"></femergenode>
    </femerge>
    <fecomposite in="edges" in2="SourceGraphic" operator="out" result="edges_complete"></fecomposite>

  </filter>
</svg>
Copy after login

Now we’re starting to look like glass, with just one piece missing.

Step 9: Yes, a bevel

We have a pretty good 3D-looking glass effect. However, the letters look flat. Let’s add one more effect and make them look more rounded.

To achieve this we’re going to create a bevelled effect.

First we’re going to use feGaussianBlur. This will blur our existing filters slightly. We’re going to use this blurred result as basis to add some feSpecularLighting. As usual, feel free to play with the numbers here and see what effects you can get! The main one you might want to change is the lighting-color attribute. The image that we’re using here is slightly dark, so we’re using a bright lighting-color. If your image was very bright, this would make the letters hard to read, so you might use a darker lighting-color in that case.

<svg>
  <filter>
  
    <!-- more stuff -->

    <fegaussianblur stddeviation="5" result="bevel_blur"></fegaussianblur>
    <fespecularlighting result="bevel_lighting" in="bevel_blur" specularconstant="2.4" specularexponent="13" lighting-color="rgba(60,60,60,.4)">
      <fedistantlight azimuth="25" elevation="40"></fedistantlight>
    </fespecularlighting>
    <fecomposite in="bevel_lighting" in2="SourceGraphic" operator="in" result="bevel_complete"></fecomposite>

  </filter>
</svg>
Copy after login

Step 10: All together now!

Finally, with all the pieces ready, we do one last feMerge to get everything in place for the finished effect!

<svg>
  <filter>

    <!-- more stuff -->

    <femerge result="complete">
      <femergenode in="edges_complete"></femergenode>
      <femergenode in="bevel_complete"></femergenode>
    </femerge>
  </filter>
</svg>
Copy after login

Here’s everything together, nicely spaced out and commented:

<!-- VISIBLE SVG -->
<svg viewbox="0 0 1890 1260">
        
  <!-- BACKGROUND IMAGE - visible -->
  <image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5946/kyoto.jpg" width="1890" x="0" height="1260" y="0"></image>
    
  <!-- DISTORTION IMAGE - clipped -->
  <image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5946/kyoto.jpg" width="1890" x="0" height="1260" y="0" clip-path="url(#clip)" filter="url(#distortion)"></image>
    
  <!-- TEXT - clipped -->
  <clippath>
    <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">KYOTO</text>
  </clippath>
    
  <!-- TEXT - visible -->
  <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" filter="url(#textFilter)">KYOTO</text>
    
</svg>

<!-- FILTERS -->
<svg>
  <filter>
    <feturbulence type="turbulence" basefrequency="0.05" numoctaves="2" result="turbulence"></feturbulence>
    <fedisplacementmap in2="turbulence" in="SourceGraphic" scale="20" xchannelselector="R" ychannelselector="G"></fedisplacementmap>
  </filter>
    
  <filter>
            
    <!-- dark edge -->
    <femorphology operator="dilate" radius="4" in="SourceAlpha" result="dark_edge_01"></femorphology>
    <feoffset dx="5" dy="5" in="dark_edge_01" result="dark_edge_03"></feoffset>
    <feflood flood-color="rgba(0,0,0,.5)" result="dark_edge_04"></feflood>
    <fecomposite in="dark_edge_04" in2="dark_edge_03" operator="in" result="dark_edge"></fecomposite>     
            
    <!-- light edge -->
    <femorphology operator="dilate" radius="4" in="SourceAlpha" result="light_edge_01"></femorphology>
    <feoffset dx="-2" dy="-2" in="light_edge_01" result="light_edge_03"></feoffset>
    <feflood flood-color="rgba(255,255,255,.5)" result="light_edge_04"></feflood>
    <fecomposite in="light_edge_04" in2="light_edge_03" operator="in" result="light_edge"></fecomposite>
          
    <!-- edges together -->
    <femerge result="edges">
      <femergenode in="dark_edge"></femergenode>
      <femergenode in="light_edge"></femergenode>
    </femerge>
    <fecomposite in="edges" in2="SourceGraphic" operator="out" result="edges_complete"></fecomposite>
          
    <!-- bevel -->
    <fegaussianblur stddeviation="5" result="bevel_blur"></fegaussianblur>
    <fespecularlighting result="bevel_lighting" in="bevel_blur" specularconstant="2.4" specularexponent="13" lighting-color="rgba(60,60,60,.4)">
      <fedistantlight azimuth="25" elevation="40"></fedistantlight>
    </fespecularlighting>
    <fecomposite in="bevel_lighting" in2="SourceGraphic" operator="in" result="bevel_complete"></fecomposite>

    <!-- everything in place -->
    <femerge result="complete">
              <femergenode in="edges_complete"></femergenode>
              <femergenode in="bevel_complete"></femergenode>
    </femerge>

  </filter>
</svg>
Copy after login

The above is the detailed content of Making a Realistic Glass Effect with SVG. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Vue 3 Vue 3 Apr 02, 2025 pm 06:32 PM

It&#039;s out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

Building an Ethereum app using Redwood.js and Fauna Building an Ethereum app using Redwood.js and Fauna Mar 28, 2025 am 09:18 AM

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

Can you get valid CSS property values from the browser? Can you get valid CSS property values from the browser? Apr 02, 2025 pm 06:17 PM

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That&#039;s like this.

Stacked Cards with Sticky Positioning and a Dash of Sass Stacked Cards with Sticky Positioning and a Dash of Sass Apr 03, 2025 am 10:30 AM

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

A bit on ci/cd A bit on ci/cd Apr 02, 2025 pm 06:21 PM

I&#039;d say "website" fits better than "mobile app" but I like this framing from Max Lynch:

Comparing Browsers for Responsive Design Comparing Browsers for Responsive Design Apr 02, 2025 pm 06:25 PM

There are a number of these desktop apps where the goal is showing your site at different dimensions all at the same time. So you can, for example, be writing

Using Markdown and Localization in the WordPress Block Editor Using Markdown and Localization in the WordPress Block Editor Apr 02, 2025 am 04:27 AM

If we need to show documentation to the user directly in the WordPress editor, what is the best way to do it?

Why are the purple slashed areas in the Flex layout mistakenly considered 'overflow space'? Why are the purple slashed areas in the Flex layout mistakenly considered 'overflow space'? Apr 05, 2025 pm 05:51 PM

Questions about purple slash areas in Flex layouts When using Flex layouts, you may encounter some confusing phenomena, such as in the developer tools (d...

See all articles