SVG 应用:使用 SVG 实现高性能阴影效果

9,084 阅读6分钟
原文链接: svgtrick.com

这篇文章来自于Slicing SVG 9 Ways,提供的思路方法确实不错,学习了下,文章没有逐字逐句翻译。主要是学习核心的思路和方法。

最近在一个项目中碰到一个问题,如下图所示:

demo地址

是一个卡片翻转的效果,仔细观察在卡片翻转的同时,在卡片的底部有一个阴影效果,阴影在卡片翻转过程中主要以下两个变化:

  • 在卡片翻转的过程中阴影会随着卡片尺寸变化有个位移的变化。
  • 阴影的尺寸也要跟随卡片的尺寸自适应。

当然要实现这样的效果,有很多方法。但是要同时兼顾性能和自适应的方法却不是很多。

我们要找的方案要同时能够兼顾下面这两个方面:

  • 性能友好。这个可以通过使用改变阴影的transformopacity属性来实现,这两个属性的变化会直接使用设备的GPU来做渲染处理。
  • 维护性好。即阴影可以非常方便的通过代码来维护,如改变阴影的颜色等。

解决方案1:通过box-shadow来实现

首先想到可能就是通过使用box-shadow来实现。简简单单一行代码就可以实现阴影效果。

不过由于box-shadow是直接作用于元素本身,这意味着如果你想通过改变阴影的blur或者是它的XY值来实现阴影的动画效果。会触发网页不停的重绘,从而会影响网页的性能。

当然还可以通过利用改变元素伪元素如:after或者是before阴影的opacity来实现,但是灵活性却不是很好。

解决方案2:通过滤镜来实现(Blur Filter)

还有一种方法是通过使用CSS滤镜来实现即Blur Filter。比如filter:blur(12px)

比如下图所示:

正常的图:

使用滤镜后:

我们可以通过控制滤镜的尺寸来实现阴影的动画效果,不过它和box-shadow同样有一个问题即改变它的相关值,会触发网页不停的重绘,从而会影响网页的性能,特别是在移动端更要注意性能的问题,稍有不剩,就会卡到爆。看来这也不是一个很好的方法。

解决方案3:SVG滤镜

在SVG中,使用滤镜也非常方便,通过下面一段代码就是用SVG中的高斯模糊滤镜来实现的一个模糊的效果:

<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  width="112" height="112">

  <!-- Define the blur -->
  <defs>
    <filter id="blur-2px">
      <feGaussianBlur
        in="SourceGraphic"
        stdDeviation="2" />
    </filter>
  </defs>

  <!-- Make a rect that uses it -->
  <rect filter="url(#blur-2px)"
    stroke="none"
    fill="#000000"
    x="6" y="6"
    width="100"
    height="100"></rect>
</svg>

在高斯模糊滤镜中,stdDeviation使用来控制模糊程度的参数,数字越大越模糊。

实际如下图所示:

并且通过改变filter的相关属性很容易来控制模糊的效果,比如模糊的程度。如果直接的增大stdDeviation的值来改变模糊程度,我们可能会得到下面的效果,滤镜效果被剪裁了:

发生了什么,在W3C官网有这样一段描述:

The bounds of this rectangle act as a hard clipping region for each filter primitive included with a given ‘filter’ element; thus, if the effect of a given filter primitive would extend beyond the bounds of the rectangle (this sometimes happens when using a ‘feGaussianBlur’ filter primitive with a very large ‘stdDeviation’), parts of the effect will get clipped. - SVG Filter Effects Spec

主要的意思是:就是讲滤镜的效果区域。这些属性定义了滤镜起作用的矩形区域。滤镜效果不会应用在超过这个区域的点上。

在滤镜中,x,y的默认值是-10%,width与height的默认值是120%。所以如果你指定滤镜模糊程度的值超过默认的120%即滤镜起作用的区域,就会出现被剪裁的效果。

要不被剪裁,就需要相应的设置x,y以及width与height的值。

译者注:其实在滤镜中还有一个重要参数需要设定即filter的filterUnits的值,userSpaceOnUse表示使用引用该filter元素的元素的用户坐标系统。如果不设置的话,那它的值默认为objectBoundingBox表示使用引用该filter元素的元素的包围盒的百分比做取值范围。

原作者文章中出现被剪裁的效果就是因为没有设置filterUnits的值为userSpaceOnUse而导致的,设置下这个值就不会出现剪裁了。

可以去这里看看实际例子:demo

下面是重头戏了,来说说怎么使用高斯模糊滤镜来实现阴影动画效果。

border-image

我们这里会使用border-image这个属性来实现阴影的效果,具体关于border-image的详细说明可以去这里看看。

具体来我会在html中编写一个类名为shadow的元素来实现阴影效果,CSS如下:

.shadow {
  position: absolute;
  width: calc(100% + 12px);
  height: calc(100% + 12px);
  left: -6px;
  top: -6px;
  opacity: 0.3;
  box-sizing: border-box;
  border-style: solid;
  border-width: 18px;
  border-image: url(images/shadow-2px.svg) 18 fill stretch;
}

简单说明下代码:

  • 这里设置元素的定位属性为绝对定位,宽高相对于父元素为100%,当然还需要把阴影的模糊值计算进去,这里模糊的值是12px,所以使用来calc属性来计算元素的真实的宽高。
  • 使用border-box。这样可以改变盒模型的计算方式,border的宽度不会被计算到元素的宽高中去。
  • 设置元素的边框为18px,下面的border-image要用到。

下面来说下border-image是怎么来填充18px边框的:

border-image: url(images/shadow-2px.svg) 18 fill stretch;
  • 首先是引入已经制作好的SVG图片url(images/shadow-2px.svg)
  • 18:用来设置边框的宽度,其单位是px,其实就像border-width一样取值,可以使用1~4个值,其具体表示四个方位的值,可以参考border-width的设置方式。
  • stretch:用来设置边框背景图片的铺放方式,类似于background-position,其中stretch是拉伸,repeat是重复,round是平铺,stretch为默认值。
  • fill:这里要说下border-image-slice指定边框图像顶部、右侧、底部、左侧内偏移量。没有具体的单位值,只要给一个单纯的数字即可,当然也可以按照百分比来给设置值。作用就是把边框图像切成9个区域:4个角、4边区域和一个中间部位,即9宫格,如果不应用fill这个可选属性值的话,那么中间第九块格子被当做透明不见。所以这里使用fill属性,指定在没有边框图片的中间部分用模糊图片来填充。

总结下

所以这里使用SVG配合border-image属性来实现了阴影的效果,并且可以通过改变阴影元素的相关值可以实现一些动画效果,性能也非常不错。当然更重要的是可维护性也不赖,可以通过编辑SVG文件来轻松的改变阴影的尺寸或者是颜色。

最后来看下一些使用这种方法来实习的阴影效果:

具体代码可以在这里查看