这篇文章来自于Slicing SVG 9 Ways,提供的思路方法确实不错,学习了下,文章没有逐字逐句翻译。主要是学习核心的思路和方法。
最近在一个项目中碰到一个问题,如下图所示:
是一个卡片翻转的效果,仔细观察在卡片翻转的同时,在卡片的底部有一个阴影效果,阴影在卡片翻转过程中主要以下两个变化:
- 在卡片翻转的过程中阴影会随着卡片尺寸变化有个位移的变化。
- 阴影的尺寸也要跟随卡片的尺寸自适应。
当然要实现这样的效果,有很多方法。但是要同时兼顾性能和自适应的方法却不是很多。
我们要找的方案要同时能够兼顾下面这两个方面:
- 性能友好。这个可以通过使用改变阴影的transform和opacity属性来实现,这两个属性的变化会直接使用设备的GPU来做渲染处理。
- 维护性好。即阴影可以非常方便的通过代码来维护,如改变阴影的颜色等。
解决方案1:通过box-shadow来实现
首先想到可能就是通过使用box-shadow来实现。简简单单一行代码就可以实现阴影效果。
不过由于box-shadow是直接作用于元素本身,这意味着如果你想通过改变阴影的blur或者是它的X和Y值来实现阴影的动画效果。会触发网页不停的重绘,从而会影响网页的性能。
当然还可以通过利用改变元素伪元素如: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文件来轻松的改变阴影的尺寸或者是颜色。
最后来看下一些使用这种方法来实习的阴影效果:
具体代码可以在这里查看。