用CSS和SVG过滤器为SVG图标添加阴影

1,425 阅读7分钟

为什么我们需要对SVG应用阴影?

  1. 阴影是一种常见的设计特征,可以帮助元素(比如图标)脱颖而出。它们可以是持久的,也可以在不同的状态下应用(例如::hover,:focus, 或:active ),以向用户表示互动。
  2. 阴影发生在现实生活中,所以它们可以被用在屏幕上,为你的元素注入一些活力,为设计增加一丝真实感

既然我们在做列表,有两种主要的方式可以将阴影应用到SVG中。

  1. 使用CSS的 [filter()](https://css-tricks.com/almanac/properties/f/filter/)属性
  2. 使用SVG<filter>

是的,这两种方法都涉及过滤器而且,是的,CSS和SVG都有自己的过滤器类型。但是,这两者之间也有一些交叉点。例如,一个CSSfilter 可以参考一个SVG<filter> ;也就是说,如果我们使用的是一个内联SVG,而不是在CSS中作为背景图片的SVG。

**你不能使用的是:**CSSbox-shadow 属性。这通常用于阴影,但是它遵循元素的矩形外边缘,而不是我们想要的SVG元素的边缘。下面是Michelle Barker的清晰解释

Two flat kitten faces in bright pink showing ears eyes and whiskers. The first kitten has a drop shadow around its box and the second kitten has a drop shadow around its path edges.

如果你使用的是SVG图标字体,那么总是有一个 [text-shadow](https://css-tricks.com/almanac/properties/t/text-shadow/).这确实会起作用。但是,让我们把注意力放在前两个方面,因为它们符合大多数的使用情况。

使用CSS过滤器的阴影

通过CSS滤镜直接对SVG应用阴影的诀窍是drop-shadow() 函数。

svg {
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));
}

这将应用一个水平方向上从3px开始,向下5px的阴影,有2px的模糊度,并且是40%的黑色。

这个浏览器支持数据来自Caniuse,它有更多细节。一个数字表示该浏览器在该版本及以上支持该功能。

桌面浏览器

浏览器火狐IE边缘浏览器浏览器
18*35没有796*

手机/平板电脑

安卓浏览器安卓火狐安卓iOS Safari
91894.4*6.0-6.1*

在一个CSS过滤器中调用一个SVG过滤器

假设我们在HTML中有一个SVG过滤器。

<svg height="0" width="0">
  
  <filter id='shadow' color-interpolation-filters="sRGB">
    <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.5"/>
  </filter>
  
</svg>

我们可以用一个CSS过滤器来调用这个SVG过滤器的ID,而不是我们之前看到的值。

svg {
  filter: url(#shadow);
}

现在这个过滤器被从HTML中提取出来,并在CSS中被引用,从而应用它。

使用SVG过滤器基元

你可能想知道我们是如何让那个SVG<filter> 工作的。为了用SVG滤镜制作一个阴影,我们使用了一个滤镜基元。SVG中的过滤器基元是一个元素,它接受某种图像或图形作为输入,然后在被调用时输出该图像或图形。它们的工作方式类似于图形编辑应用程序中的过滤器,但在代码中,只能在SVG [<filter>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter)元素内使用。

SVG中有很多不同的过滤基元。我们正在使用的是<feDropShadow> 。我可以让你通过看名字来猜测它的作用。

所以,类似于我们用CSS过滤器做的这样的事情。

svg {
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));
}

...我们可以用<feDropShadow> SVG过滤器基元完成同样的工作。有三个关键属性值得一提,因为它们有助于定义阴影的外观。

  • dx - 这将使阴影的位置沿X轴移动。
  • dy - 这将使阴影的位置沿Y轴移动。
  • stdDeviation - 这定义了水滴阴影的模糊操作的标准偏差。还有其他我们可以使用的属性,比如用于设置阴影颜色的 ,以及用于设置阴影不透明度的 。flood-color flood-opacity

这个例子包括三个<filter> ,每个元素都有自己的<feDropShadow> 过滤器基元。

使用SVG过滤器

SVG过滤器是非常强大的。我们刚刚看了<feDropShadow> ,这当然是非常有用的,但是它们能做的事情太多了(包括类似Photoshop的效果),而且我们只为阴影得到的东西子集也很广泛。让我们来看看一些,比如彩色阴影和嵌入阴影。

让我们以Twitter标志的SVG标记为例。

<svg class="svg-icon" viewBox="0 0 20 20">
  <path fill="#4691f6" d="M18.258,3.266c-0.693,0.405-1.46,0.698-2.277,0.857c-0.653-0.686-1.586-1.115-2.618-1.115c-1.98,0-3.586,1.581-3.586,3.53c0,0.276,0.031,0.545,0.092,0.805C6.888,7.195,4.245,5.79,2.476,3.654C2.167,4.176,1.99,4.781,1.99,5.429c0,1.224,0.633,2.305,1.596,2.938C2.999,8.349,2.445,8.19,1.961,7.925C1.96,7.94,1.96,7.954,1.96,7.97c0,1.71,1.237,3.138,2.877,3.462c-0.301,0.08-0.617,0.123-0.945,0.123c-0.23,0-0.456-0.021-0.674-0.062c0.456,1.402,1.781,2.422,3.35,2.451c-1.228,0.947-2.773,1.512-4.454,1.512c-0.291,0-0.575-0.016-0.855-0.049c1.588,1,3.473,1.586,5.498,1.586c6.598,0,10.205-5.379,10.205-10.045c0-0.153-0.003-0.305-0.01-0.456c0.7-0.499,1.308-1.12,1.789-1.827c-0.644,0.28-1.334,0.469-2.06,0.555C17.422,4.782,17.99,4.091,18.258,3.266" ></path>
</svg>

我们将需要一个<filter> 元素来做这些效果。这需要在HTML中的一个<svg> 元素内。一个<filter> 元素永远不会在浏览器中直接呈现--它只被用作可以通过SVG中的filter 属性或CSS中的url() 函数来引用的东西。

下面是显示SVG过滤器并将其应用于源图像的语法。

<svg width="300" height="300" viewBox="0 0 300 300">

  <filter id="myfilters">
    <!-- All filter effects/primitives go in here -->
  </filter>

  <g filter="url(#myfilters)">
    <!-- Filter applies to everything in this group -->
    <path fill="..." d="..." ></path>
  </g>

</svg>

filter 元素是用来容纳作为子元素的过滤器基元的。它是一系列过滤操作的容器,这些过滤操作被组合起来形成一个过滤效果。

这些滤镜基元在一个或多个输入上执行单一的基本图形操作(例如,模糊、移动、填充、组合或扭曲)。它们就像积木一样,每个SVG滤镜都可以与其他滤镜结合起来使用,从而创造出一种效果。<feGaussianBlur> 是一个常用的滤镜基元,用于添加模糊效果。

假设我们用<feGaussianBlur> 来定义下面这个SVG过滤器。

<svg version="1.1" width="0" height="0">
  <filter id="gaussian-blur">
    <feGaussianBlur stdDeviation="1 0" />
  </filter>
</svg>

当应用于一个元素时,这个过滤器会创建一个高斯模糊,在X轴上以1px 为半径模糊元素,但在Y轴上没有模糊。

在一个单一的过滤器内使用多个基元是可能的。这将创造出有趣的效果,然而,你需要让不同的基元相互认识。Bence Szabó有一套疯狂酷炫的图案,他就是这样创建的。

组合多个滤波器基元时,第一个基元使用原始图形 (SourceGraphic) 作为其图形输入。任何后续的基元都使用它之前的滤波器效果的结果作为其输入。以此类推。但是,我们可以通过使用基元元素上的inin2result 属性获得一些灵活性。史蒂文-布拉德利(Steven Bradley)有一篇关于过滤器基元的优秀文章,可以追溯到2016年,但今天仍然适用。

今天我们有17个基元可以使用。

  • <feGaussianBlur>
  • <feDropShadow>
  • <feMorphology>
  • <feDisplacementMap>
  • <feBlend>
  • <feColorMatrix>
  • <feConvolveMatrix>
  • <feComponentTransfer>
  • <feSpecularLighting>
  • <feDiffuseLighting>
  • <feFlood>
  • <feTurbulence>
  • <feImage>
  • <feTile>
  • <feOffset>
  • <feComposite>
  • <feMerge>

注意所有这些基元上的fe 前缀。这代表的是滤镜效果。理解SVG滤镜是一项挑战。像插入式阴影这样的效果需要一个冗长的语法,如果没有对数学和色彩理论的透彻理解,就很难掌握。(Rob O'Leary的"深入了解阴影 "是一个很好的开始。)

与其跑到这些东西的兔子洞里,我们不如用一些预制的过滤器来工作。幸运的是,周围有很多现成的SVG过滤器。

嵌入阴影

为了在Twitter标志上使用滤镜效果,我们需要在我们的 "SVG源文件 "中声明它的唯一ID,以便在我们的<filter> 标签中进行引用。

<filter id='inset-shadow'>
  <!-- Shadow offset -->
  <feOffset
    dx='0'
    dy='0'
  />

  <!-- Shadow blur -->
  <feGaussianBlur
    stdDeviation='1'
    result='offset-blur'
  />

  <!-- Invert drop shadow to make an inset shadow -->
  <feComposite
    operator='out'
    in='SourceGraphic'
    in2='offset-blur'
    result='inverse'
  />
  
  <!-- Cut color inside shadow -->
  <feFlood
    flood-color='black'
    flood-opacity='.95'
    result='color'
  />
  <feComposite
    operator='in'
    in='color'
    in2='inverse'
    result='shadow'
  />

  <!-- Placing shadow over element -->
  <feComposite
    operator='over'
    in='shadow'
    in2='SourceGraphic'
  />
</filter>

这里面有四个不同的基元,每一个都执行不同的功能。但是,合在一起,它们实现了一个嵌入阴影。

现在我们已经创建了这个嵌入阴影过滤器,我们可以把它应用到我们的SVG中。我们已经看到了如何通过CSS来应用它。比如说。

.filtered {
  filter: url(#myfilters);
}

/* Or apply only in certain states, like: */
svg:hover, svg:focus {
  filter: url(#myfilters);
} 

我们也可以在SVG语法中直接使用filter 属性来应用SVG<filter> 。这就像

<svg>

  <!-- Apply a single filter -->
  <path d="..." filter="url(#myfilters)" />

  <!-- Or apply to a whole group of elements -->
  <g filter="url(#myfilters)">
    <path d="..." />
    <path d="..." />
  </g>
</svg>

总结

最后做个比较。

  • CSS过滤器更容易使用,但局限性也更大。例如,我认为不可能用drop-shadow() 函数来添加一个嵌入的阴影。
  • SVG过滤器要强大得多,但也要复杂得多,而且需要在HTML中的某个地方设置<filter>
  • 它们都有很好的浏览器支持,在所有的现代浏览器上都有很好的表现,尽管SVG过滤器(令人惊讶地)有最深的浏览器支持。

在这篇文章中,我们已经看到了为什么以及如何在SVG图标上应用阴影,并分别举了例子。你是否也做过这样的事情,但做的方式与我们看的都不一样?你是否尝试过做一个阴影效果,但你发现不可能完成?请分享吧