使用 SVG 创建逼真的玻璃效果

Avatar of David Fitzgibbon
David Fitzgibbon

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

我爱 SVG。当然,代码乍一看可能看起来很密集且难以理解,但当你了解它之后,你会发现结果的美妙之处。好处是这些结果都在代码中,因此可以连接到 CMS。您的设计师可以高枕无忧,因为他们不必为网站上的每篇文章或产品重新制作效果。

今天我想向您展示我是如何想出这种玻璃文本效果的。

The word Kyoto that is translucent and stacked on top an image of the city.

步骤 0:耐心和空间

SVG 可能需要花费大量时间来学习,尤其是在您刚开始学习时(如果您是,Chris 的书 是一个不错的起点)。它实际上是一门全新的语言,特别是对于缺乏设计能力的人来说,需要了解许多新的技术和注意事项。但是,与 HTML 一样,您会发现有一些工具可以帮助我们更轻松地理解 SVG。因此,请耐心一点,不断尝试!

此外,给自己一些空间。字面意思。SVG 代码很密集,因此我喜欢使用两三行新行来间隔代码。这使代码更易读,并帮助我看到不同的部分是如何分开的,从而减少视觉干扰。哦,还要使用注释来标记您在文档中的位置。这可以帮助整理您的思路并记录您的发现。

我已经为学习这种玻璃效果过程中我们要介绍的每个步骤制作了演示,作为一种帮助巩固我们正在学习内容的方法。

好了,现在我们已经做好心理准备了,让我们进入正题!

步骤 1:将基本图像放置到位

首先:我们需要一个图像作为我们玻璃效果的背景。这里我们有一个 <svg> 元素,以及其中包含的 <image>。这与在 HTML 中添加 <img> 类似。您会注意到 SVG 元素中 viewBox 属性和 <image> 元素的尺寸相同。这确保了 <image> 与我们链接的实际图片的大小完全相同。

需要注意的是一个关键的区别:我们正在链接到一个图像。SVG 文件本身不绘制光栅图像,但我们可以在 SVG 代码中引用一个,并确保该资产位于我们指向的位置。如果您以前使用过 Adobe InDesign,它就像在布局中链接到一个图像资产一样——图像在 InDesign 布局中,但资产本身实际上存储在其他位置。

查看 CodePen:
SVG 玻璃文本效果 – 基本图像放置到位
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 2:扭曲图像

到目前为止很简单,但这里事情开始变得复杂,因为我们要向刚刚插入的图像添加一个滤镜。这个滤镜将扭曲图像。如果您仔细观察上一步中的演示和这一步中的演示之间的区别,您会发现图像中物体边缘有点粗糙和波浪形。那是滤镜在起作用!

首先,我们创建一个另一个 <svg> 来保存滤镜。这意味着,如果我们想在页面上的多个元素上重用我们的滤镜,我们完全可以!

我们的第一个滤镜(#displacement)将扭曲我们的图像。我们将使用 feTurbulencefeDisplacementMapSara Soueidan 对这两个滤镜的解释 比我在这篇文章中解释得更好。Beau Jackson 还写了一篇很好的文章,展示了如何使用它们来创建云效果。总之,这两个滤镜通常一起使用,我认为它们就像某种东西需要“摇晃”一样。

滤镜容器到位后,我们只需要在 <image> 上使用 filter 属性应用该滤镜,神奇!

<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 id="distortion">
    <feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="2" result="turbulence"/>
    <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="20" xChannelSelector="R" yChannelSelector="G"/>
  </filter>

  <!-- more stuff -->

</svg>

查看 CodePen:
SVG 玻璃文本效果 – 图像扭曲
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 3:裁剪文本

我们不希望整个图像都被扭曲。我们要将扭曲的 <image> 的形状裁剪到一些文本的形状。这实际上将是“透过”玻璃看到的图片的一部分。

为此,我们需要在 <clip-path> 中添加一个 <text> 元素,并为其指定一个 id。在 <image>clip-path 中调用此 id 现在将其形状限制为我们的 <text> 的形状。太好了!

查看 CodePen:
SVG 玻璃文本效果 – 文本裁剪
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 4:显示完整图像

好了,我们将扭曲的 <image> 裁剪到 <text> 上,但这很好,但现在其余的图像不见了。不好。

我们可以通过添加与我们现有的 <image> 相同的 <image> 的副本,但没有 clip-pathfilter 属性,在我们的现有 <image> 之前进行抵消。这是我喜欢添加一些不错的注释以保持整洁的地方。这个想法就像在我们的现有内容上放置一个透明层。

Illustration showing one image overlaid on the other.

我知道,我知道,这并不整洁,而且我们重复了自己。理想情况下,我们将直接在 <text> 元素上设置我们的滤镜,并使用 in="BackgroundImage 属性为 feDisplacementMap 扭曲文本后面的内容,而无需额外的元素。不幸的是,这在浏览器中的支持较差,因此我们将使用多个图像。

<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>

查看 CodePen:
SVG 玻璃文本效果 – 扭曲完成
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 5:再次放置文本

接下来,我们将像上一步中的图像一样复制我们的文本。不幸的是,由于文本在 clip-path 中,因此它现在无法进行渲染。我保证这是我们最后一次像这样复制内容!

现在我们应该看到一个看起来像普通图像,上面有黑色文本。如果我们已经制作的 <image> 上的扭曲 filter 是我们“透过”玻璃可以看到的东西,那么我们的新的 <text> 将是玻璃本身。

<svg>

  <!-- more stuff -->

  <!-- TEXT - clipped -->
  <clipPath id="clip">
    <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>

查看 CodePen:
SVG 玻璃文本效果 – 文本再次放置到位
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 6:创建文本的暗边

这里事情开始变得令人兴奋,至少对我来说是!🤓

我们要在文本元素上创建一个暗边,与亮边(我们将在下一步中介绍)配对,将为文本相对于图像的外观增加深度。

我们需要为我们的 <text> 创建一个新的 filter,因此让我们在滤镜的 SVG 元素中创建一个,并为其指定一个 id="textFilter,并将其链接到 <text> 元素的 filter 属性。

SVG 从背景到前景工作,因此我们要放入滤镜中的第一件事是玻璃将具有的阴影,因为那是最靠后的。我要坦诚地告诉你,这个步骤相当复杂,但我们将一步一步地完成它。

对于这种效果,我们使用四个滤镜基元:feMorphologyfeOffsetfeFloodfeComposite

首先是 feMorphology。我们使用它使文本更粗。在下面的演示中,注释掉接下来的三个基元(feOffsetfeFloodfeComposite),然后使用它。我将 radius="4" 的值用于实现玻璃效果,但看看如果将它设置为 1…或 100 会发生什么!

feOffset 用于将前一个基本图形 (feMorphology) 中的所有“像素”沿 x 轴或 y 轴移动。值 dx="5"dy="5" 分别将“像素”分别向右移动到 x 轴和 y 轴。数字越大,移动距离越远。将 dx 设置为负数,“像素”将向左移动。负 dy 则会向上移动!再次强调,这是一种你在玩弄它们的时候开始学习的东西。

我之所以在“像素”周围加引号,是因为它们不像你在 CSS 中预期的屏幕像素。相反,它们指的是我们在父 <svg> 上设置的尺寸。我认为它们是百分比。我们在示例中使用了这些设置 viewBox="0 0 1890 1260"。这意味着我们的 <svg> 宽度为 1890 个“像素”。如果我们设置 dx="189",则表示我们将元素移动到 SVG 宽度 10% 的位置(1890 除以 189)。

feFlood 太棒了。如果你想用颜色填充屏幕,这是你需要使用的基本图形!你可能想知道为什么我们在应用它后无法读取我们的文本。这是因为你只能看到最后创建的滤镜基本图形的结果。之前每个基本图形的结果都与我们的 <text> 元素相关。feFlood 的结果就像它的名字一样:一片色彩。它不知道你之前做了什么,也不关心 — 它只是用颜色填充一个区域。

这就是有些人开始对 SVG 感到沮丧的地方。当你无法看到它的时候,很难进行创作!相信我,当你使用 SVG 的次数越多,你就会越习惯这种方式。事实上,接下来的几步将需要我们依赖它并相信一切仍然到位。

feComposite 将解决这个问题。它做什么?MDN 将其描述为

SVG 滤镜基本图形使用 Porter-Duff 合成操作之一在图像空间中逐像素组合两个输入图像:over、in、atop、out、xor 和 lighter。

对我来说,这都是胡言乱语。我认为它是通过 in2 的颜色/alpha 来影响 in 的 alpha 层。

有了它,我们就可以再次看到拼写的文本,而且由于我们使用的颜色稍微透明,我们甚至可以看到透过的扭曲的“玻璃”效果。太棒了!

<svg>

  <!-- more stuff -->
    
  <!-- dark edge -->
  <feMorphology operator="dilate" radius="4" in="SourceAlpha" result="dark_edge_01" />
    <feConvolveMatrix order="3,3" kernelMatrix=
      "1 0 0 
      0 1 0
      0 0 1" in="dark_edge_01" result="dark_edge_02" />
    <feOffset dx="5" dy="5" in="dark_edge_02" result="dark_edge_03"/>
    <feFlood flood-color="rgba(0,0,0,.5)" result="dark_edge_04" />
    <feComposite in="dark_edge_04" in2="dark_edge_03" operator="in" result="dark_edge" />
    
  </filter>

  <!-- more stuff -->

</svg>

查看笔:
SVG 玻璃文字效果 — 深色边缘
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 7:让我们来做亮边

这与我们刚才做的几乎相同,但我们将使用负 dx/dy 值将形状向上和向左移动。这次我们还设置了一个稍微白色的颜色。我们的目标是获得良好的深度效果。

我们再次处于只能看到滤镜基本图形的最新结果,而无法看到深色边缘的位置!feComposite 不是我们想要用来将它们组合在一起的方法,因为我们不希望深色边缘的 alpha 被亮边着色… 我们希望看到两者!这导致我们…

<svg>
  <filter id="textFilter">

    <!-- more stuff -->

      <feMorphology operator="dilate" radius="4" in="SourceAlpha" result="light_edge_01" />
      <feConvolveMatrix order="3,3" kernelMatrix=
      "1 0 0 
        0 1 0
        0 0 1" in="light_edge_01" result="light_edge_02" />
      <feOffset dx="-2" dy="-2" in="light_edge_02" result="light_edge_03"/>
      <feFlood flood-color="rgba(255,255,255,.5)" result="light_edge_04" />
      <feComposite in="light_edge_04" in2="light_edge_03" operator="in" result="light_edge" />

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

查看笔:
SVG 玻璃文字效果 — 亮边
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 8:组合边缘

feMerge!它是一个英雄。它允许我们获取任意数量的基本图形结果并将它们合并,从而生成新的图像。哇,我们现在可以看到深色和亮色边缘都组合在一起了!

但是,我们希望它们是边缘,而不是都填充整个文本,因此我们需要移除原始 <text> 占用的空间。接下来我们需要另一个 feComposite 来切除原始 SourceGraphic。因为我们使用 feMorphology 来加粗字母以创建边缘,所以我们现在可以从 feMerge 的结果中切除原始字母形状。

<svg>
  <filter id="textFilter">

    <!-- more stuff -->

    <feMerge result="edges">
      <feMergeNode in="dark_edge" />
      <feMergeNode in="light_edge" />
    </feMerge>
    <feComposite in="edges" in2="SourceGraphic" operator="out" result="edges_complete" />

  </filter>
</svg>

查看笔:
SVG 玻璃文字效果 — 组合边缘
by David Fitzgibbon (@loficodes)
on CodePen.

现在我们开始看起来像玻璃,只差最后一步了。

步骤 9:是的,一个斜面

我们有一个非常好的 3D 玻璃效果。但是,字母看起来很平。让我们添加一个额外的效果,让它们看起来更圆润。

为了实现这一点,我们将创建一个斜面效果。

首先,我们将使用 feGaussianBlur。这将稍微模糊我们现有的滤镜。我们将使用这个模糊的结果作为基础来添加一些 feSpecularLighting。像往常一样,请随意玩弄这里的数字,看看你能获得什么效果!你可能想要更改的主要属性是 lighting-color 属性。我们在这里使用的图像是稍微暗的,因此我们使用了一个明亮的 lighting-color。如果你的图像非常亮,这将使字母难以阅读,因此在这种情况下你可能需要使用更暗的 lighting-color

<svg>
  <filter id="textFilter">
  
    <!-- more stuff -->

    <feGaussianBlur stdDeviation="5" result="bevel_blur" />
    <feSpecularLighting result="bevel_lighting" in="bevel_blur" specularConstant="2.4" specularExponent="13" lighting-color="rgba(60,60,60,.4)">
      <feDistantLight azimuth="25" elevation="40" />
    </feSpecularLighting>
    <feComposite in="bevel_lighting" in2="SourceGraphic" operator="in" result="bevel_complete" />

  </filter>
</svg>

查看笔:
SVG 玻璃文字效果 — 斜面
by David Fitzgibbon (@loficodes)
on CodePen.

步骤 10:全部一起!

最后,当所有部件都准备好后,我们进行最后一次 feMerge,将所有内容都放到位,完成最终效果!

<svg>
  <filter id="textFilter">

    <!-- more stuff -->

    <feMerge result="complete">
      <feMergeNode in="edges_complete" />
      <feMergeNode in="bevel_complete" />
    </feMerge>
  </filter>
</svg>

以下是所有内容,间隔合理,并添加了注释

<!-- 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 id="clip">
    <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 id="distortion">
    <feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="2" result="turbulence"/>
    <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="20" xChannelSelector="R" yChannelSelector="G"/>
  </filter>
    
  <filter id="textFilter">
            
    <!-- dark edge -->
    <feMorphology operator="dilate" radius="4" in="SourceAlpha" result="dark_edge_01" />
    <feOffset dx="5" dy="5" in="dark_edge_01" result="dark_edge_03"/>
    <feFlood flood-color="rgba(0,0,0,.5)" result="dark_edge_04" />
    <feComposite in="dark_edge_04" in2="dark_edge_03" operator="in" result="dark_edge" />     
            
    <!-- light edge -->
    <feMorphology operator="dilate" radius="4" in="SourceAlpha" result="light_edge_01" />
    <feOffset dx="-2" dy="-2" in="light_edge_01" result="light_edge_03"/>
    <feFlood flood-color="rgba(255,255,255,.5)" result="light_edge_04" />
    <feComposite in="light_edge_04" in2="light_edge_03" operator="in" result="light_edge" />
          
    <!-- edges together -->
    <feMerge result="edges">
      <feMergeNode in="dark_edge" />
      <feMergeNode in="light_edge" />
    </feMerge>
    <feComposite in="edges" in2="SourceGraphic" operator="out" result="edges_complete" />
          
    <!-- bevel -->
    <feGaussianBlur stdDeviation="5" result="bevel_blur" />
    <feSpecularLighting result="bevel_lighting" in="bevel_blur" specularConstant="2.4" specularExponent="13" lighting-color="rgba(60,60,60,.4)">
      <feDistantLight azimuth="25" elevation="40" />
    </feSpecularLighting>
    <feComposite in="bevel_lighting" in2="SourceGraphic" operator="in" result="bevel_complete" />

    <!-- everything in place -->
    <feMerge result="complete">
              <feMergeNode in="edges_complete" />
              <feMergeNode in="bevel_complete" />
    </feMerge>

  </filter>
</svg>

查看笔:
SVG 玻璃文字效果
by David Fitzgibbon (@loficodes)
on CodePen.