✨让网页 Bling-Bling 的SVG滤镜 - 总览篇

989 阅读11分钟

这是我所写的SVG滤镜相关的第一篇文章。本想一篇文章写完,后来发现这SVG滤镜里的内容多的可不是一点半点,所以还是分开写吧👻

回顾一下

上一篇文章里,笔者介绍了CSS滤镜,仅仅通过为元素的CSS filter 属性设置一个滤镜的关键字即可轻松地实现一些看起来很OK的效果。

上次介绍了blurbrightnesscontrastgrayscaleinvertopacitysaturatesepiadrop-shadow以及hue-rotate这几个滤镜属性值,事实上目前(Chrome80 / Firefox76)CSS filter属性能够接受的属性值以及W3C所规定的关键字也的确只有这几个,但别忘了filter属性还可以通过url()来引用一个来自外部的滤镜。

外部滤镜,其实也就是这篇文章所介绍的SVG滤镜。

上一篇文章中所说,如果你有使用过Illustrator(俗称Ai)这款软件来处理过矢量图形,可以在 效果 - SVG滤镜- 应用 SVG 滤镜 菜单下找到一些示例,可以参照它们来创建自己的SVG滤镜。

来看张图

image 在这张图里,笔者总结了在SVG中与滤镜相关的所有元素(准确来说应该称作“原语”,下同),并对它们进行了简单分类。可以发现,滤镜原语在SVG元素中是一个很庞大的家族,它们都以"fe"开头,共有17个,均放置于<svg><filter>标签下。滤镜原语可以单独使用,也可以通过类似节点相互连接(链状或网状)的方式,组合各自的中间结果,来输出最终图像,从而产生出一个复杂滤镜。

SVG Filter Builder 是一个可以通过可视化方式搭配出SVG滤镜的网站,因此我们可以直观地看到滤镜原语间的节点连接。不过这个项目好像是闭源的。

因此为了尝试学习类似的滤镜搭建流程,笔者在尝试搭建出一个能够对SVG滤镜进行可视化编辑的小工具,类似一个节点连接工具,在下方示例中你可以看到它的身影。

滤镜根元素 - <filter>

<filter> 是SVG滤镜元素的根标签,存在于 <svg> 元素的子层级中。

<filter> 标签中,可以定义滤镜在使用元素处生效范围、坐标空间及其下方所包含的滤镜元素(原语)的坐标空间等等属性。

  • filterUnits

    定义了滤镜生效范围的坐标系统(即滤镜根元素的xywidthheight属性将如何取值)

    默认值为objectBoundingBox,可选值包括:objectBoundingBox|userSpaceOnUse

    objectBoundingBox - 相对应用滤镜的元素所占区域来设置滤镜区域,设置该值后,下方xywidthheight四个值应当为分数(百分比、小数)值。

    userSpaceOnUse - 相对<svg>标签所占区域来设置滤镜区域(笔者实测,但和标准上的表述可能略有出入)。

  • xy

    表示滤镜生效范围的起始坐标。

    默认值为-10%

  • widthheight

    表示滤镜生效范围的终止坐标。

    默认值为120%

    滤镜生效范围是一个矩形区域。这个矩形区域相对的坐标系统由filterUnits指定,具体范围由xywidthheight四个属性值定义。你可以以一种通俗易懂但不算很准确的方式来片面地理解 —— 假设我们为一个元素设置滤镜,那么当滤镜生效后,滤镜效果超过元素本身后,滤镜效果将会从元素本身扩张多少。当然滤镜生效范围可以设置比元素范围更小,因此这里的理解是片面的。但无论如何,无论是元素本身或滤镜效果,只要产生的内容超过这个滤镜生效范围,超出部分都将会被不留情面地截掉。参见Filter Region

    在这里我们来看个有关滤镜区域的几个小例子:

    【例1-1】

    原图

    image.png

    【例1-2】

    下面为这张图片设置投影滤镜<feDropshadow>

    开启滤镜,不设置滤镜范围 —— 此时滤镜范围将使用默认值x="-10%" y="-10%" width="120%" height="120%"

    <filter id="dropShadowFilter">
        <feDropShadow dx="0" dy="0" stdDeviation="50"></feDropShadow>
    </filter>
    

    image.png 我们也可以看下图解,了解一下这里生效的滤镜的范围 image.png

    【例1-3】

    开启滤镜,设置滤镜范围

    <filter id="dropShadowFilter" x="-20%" y="-50%" width="150%" height="200%">
        <feDropShadow dx="0" dy="0" stdDeviation="50"></feDropShadow>
    </filter>
    

    image.png

    同样我们看一下图解 image.png

    这里的例子,我们所设置的xywidthheight四个值均为百分比,这是因为由于我们目前没有为filterUnits设置值,所以默认使用了它的默认值objectBoundingBox —— 即相对于设置滤镜的元素的包围盒来作为坐标系统,而要比较方便地相对于这个大小不定的元素来设置滤镜,显然用百分比是一种比较好的方式。同样,你也可以使用与百分比等效的小数值(0.1===10%;此时填写整数值也将按照小数来看待,例如20===2000%) —— 事实上规范中也的确提到这里应该使用类似分数的值。

    【例1-4】

    我们来试试将filterUnits的值改为userSpaceOnUse

    <filter id="dropShadowFilter" filterUnits="userSpaceOnUse" x="120" y="220" width="100%" height="100%">
        <feDropShadow dx="0" dy="0" stdDeviation="50"></feDropShadow>
    </filter>
    

    image.png

    image.png

    在将filterUnits的值改为userSpaceOnUse之后,滤镜范围所参照的坐标系也发生了一些变化,此处将按照<svg>的坐标来设置。例如这里设置的滤镜范围起始坐标是x="120" y="220",滤镜范围尺寸为width="100%" height="100%",因此由图中你可以看到,这里的起始坐标、范围尺寸参照的是应用滤镜的元素所在的<svg>元素,

  • primitiveUnits

    定义了滤镜原语坐标系统(即滤镜原语上的的xywidthheight属性将如何取值)

    默认值为userSpaceOnUse,可选值包括:objectBoundingBox | userSpaceOnUse

    与滤镜父元素相似,每个原语也可以有自己的生效范围。

原语

这些元素是放在<filter>元素之下的,使用方式类似:

<filter id="filter-name">
    <!-- feFlood 即为一个滤镜原语 -->
    <feFlood flood-color="#f00" />
</filter>

首先我们来看一看他们共有的一些属性吧。

  • xy

    表示原语生效范围的起始坐标。

    默认值为滤镜范围的起始点。

  • widthheight

    表示原语生效范围的终止坐标。

    默认值为滤镜范围的尺寸。

    规范中规定xy默认值为0widthheight100%,实际测试好像不准确?😵

    原语生效范围也是一个矩形区域,这个矩形区域相对的坐标系统由根元素上的primitiveUnits指定,具体范围由这里的xywidthheight四个属性值定义,参见Filter Primitive Subregion

    我们接着【例1-1】中的原图示例来继续看一看有关原语生效范围的例子。

    【例1-5】

    这一次我们为这个示例设置的滤镜填充滤镜<feFlood>

    <filter id="floodFilter">
        <feFlood flood-color="#f00" flood-opacity="0.5" />
    </filter>
    

    image.png

    这里我们为目标元素设置了填充滤镜(红色填充部分),但我们可以看到的是,原本的图片在此没有任何体现 —— 看起来这里的例子与【例1-1】是毫不相干的。

    【例1-6】

    因此为了能让原图在本例中展现出来,我们需要将上个例子中的滤镜改复杂一些,通过其它原语引入原图SourceGraphic

    <filter id="floodFilter">
        <feTile in="SourceGraphic" result="tile" />
        <feFlood flood-color="#f00" flood-opacity="0.5" result="flood" />
        <feMerge>
            <feMergeNode in="tile" />
            <feMergeNode in="flood" />
        </feMerge>
    </filter>
    

    image.png

    这里你可能看到了一些新的内容。我们使用<feTile>引入原图,同时使用<feMerge>来对<feTile><feFlood>的滤镜结果进行合并(类似Photoshop的图层),这样就引入了原图。

    可能你对这一代码片段看不太懂,不过没关系,文章才刚刚开始,看到后面你或许就了解了~

    下面我们来设置<feFlood>的原语生效范围。首先我们可以看到,由于原语生效范围默认值是和滤镜生效范围重合的,因此上图中被应用滤镜的元素都被填充了半透明红色,示意图:

    image.png

    【例1-7】 我们可以对这个范围进行一些移动或改变大小,例如将原语范围的起始点移到(40px, 10px)位置

    <filter id="floodFilter">
        <feTile in="SourceGraphic" result="tile"></feTile>
        <feFlood flood-color="#f00" flood-opacity="0.5" result="flood" x="40px" y="10px"></feFlood>
        <feMerge>
            <feMergeNode in="tile"></feMergeNode>
            <feMergeNode in="flood"></feMergeNode>
        </feMerge>
    </filter>
    

    image.png

    此时仅有滤镜生效范围<feFlood>原语生效范围重合部分产生了填充滤镜效果,其余部分被截断。如示意图:

    image.png

    因此从这个例子可以看出,当原语生效范围(<filter>元素中的primitive属性)值为userSpaceOnUse时,所设置的区域将参照<svg>元素的位置来设置。

    【例1-8】 例如如果希望<feFlood>原语的生效范围可以占满整个<svg>元素,则可以将它的范围设为x="0" y="0" width="100%" height="100%",最终结果下:

    image.png

    如示意图:

    image.png

    可以看到效果和【例1-6】完全相同。正如上文所述,仅有滤镜生效范围原语生效范围重合部分产生了填充滤镜效果。也正如上文所述以及规范中所提及,滤镜生效范围为被称作“Filter Region”(滤镜区域),原语生效范围被称作“Filter Primitive Subregion”(滤镜原语子区域),二者具有父子关系,因此子区域大小不会越过父区域,因此<feFlood>的效果最终也就局限在了滤镜生效范围内。

    【例1-9】 当把<primitiveUnits>值设为objectBoundingBox的时候,原语生效范围又将如何取值呢?

    <filter id="floodFilter" primitiveUnits="objectBoundingBox">
        <feTile in="SourceGraphic" result="tile"></feTile>
        <feFlood flood-color="#f00" flood-opacity="0.5" result="flood" x="0" y="0" width="100%" height="100%"></feFlood>
        <feMerge>
            <feMergeNode in="tile"></feMergeNode>
            <feMergeNode in="flood"></feMergeNode>
        </feMerge>
    </filter>
    

    image.png

    可以看到,此时原语生效范围将完全参照应用滤镜的元素的尺寸来设置。如示意图:

    image.png

    filterUnits设为objectBoundingBox类似,当primitiveUnits值也设为objectBoundingBox时,xywidthheight值仅能为百分比或是等效的小数。

    【例1-10】 同样我们来对这个范围稍稍进行一些调整,范围设为x="-10%" y="0" width="50%" height="75%",可得到:

    image.png

    image.png

  • result

    表示经过这个滤镜原语处理过后得到的图像的结果。可以认为,它定义了一个字符串变量,用于保存对图像处理结果的一个引用;我们可以把这个字符串变量传递给下一个滤镜原语的in/in2(如果下一个原语有这两个属性之一),以用于接下来进一步的处理流程,而且这种传递可以是链式的。

  • in

    in用于接受来自于上一个滤镜原语的处理结果(result中定义的字符串),以作为当前滤镜原语的输入来源。在具有图像处理功能的原语中都存在in属性。

对于result属性与in属性,可以参照下图形象地来理解:

image.png

这个滤镜源码为:

<filter xmlns="http://www.w3.org/2000/svg" id="filter">
    <feTile in="SourceGraphic" result="4A255B61-4297-4E34-8029-CAD5991FE52D"/>
    <feConvolveMatrix in="4A255B61-4297-4E34-8029-CAD5991FE52D" kernelMatrix="0 1 0, 1 1 1, 0 1 0" order="3, 3" divisor="1" edgeMode="none" kernelUnitLength="2" preserveAlpha="true" result="A19A3921-6AE4-4B2C-99F4-85413D185F73"/>
    <feGaussianBlur in="A19A3921-6AE4-4B2C-99F4-85413D185F73" stdDeviation="20" result="BC28BFA6-FE4C-45F4-B5B4-86490BF55F38"/>
</filter>

来解释一下,首先我们从<feTile>原语得到了一个源图像使用滤镜后的结果(因为这里没有指定第一步的输入来源,因此输入来源默认为源图像SourceGraphic),然后我们把这个结果输入到<feConvolveMatrix>原语中进行处理,再次得到一个结果,最后我们再把这一步得到的结果输入到<feGaussianBlur>原语中,得到最终结果。

  • color-interpolation-filters

    表示在原语在对图像进行处理过程中将要使用到的颜色空间。

    默认值为auto,可选值为auto | linearRGB | sRGBauto意为由浏览器自己决定,参考了规范以及网上已有的各类资料,并大概自己试了一下,Chrome 91与Firefox 91 为linearRGB;但日常处理图片更加常用的是sRGB。需要注意的是:

    1. 图像处理过程中如果选择了错误的颜色空间,那么可能会产生错误的处理结果 —— 后面的文章里有鲜活的例子,所以为了在下面文章中少一些累赘,在不特别说明的情况下,我们覆盖浏览器默认值linearRGB,约定该值默认为sRGB`🥴
    2. 根据规范所述,这个属性只对有图像进行颜色处理的原语才生效。在此可以先了解一下,<feOffset><feImage><feTile><feFlood>原语没有对图像进行颜色处理,所以这个属性值设置与否都不会有效果。

限于篇幅,下文介绍到各类滤镜原语时将不再介绍和列举xywidthheightresult以及color-interpolation-filters属性。

为便于理解,接下来的文章中有关滤镜原语的可视化介绍方式将按照如下图所示的节点连线图进行。图例:

image.png

这里我们约定:当连线输入端口(对应属性包括in,左侧橙色端口)中没有输入值的时候,输入值默认为源图像SourceGraphic;连线输出端口(右侧蓝色端口)对应属性为result


以上就是SVG 滤镜总览篇的全部内容了。接下来的文章介绍的内容实际上>将围绕原语中in所接受的值来展开,欢迎继续阅读。 如果文章中有纰漏,还请大佬拍砖~


总目录:

  1. CSS滤镜篇
  2. 总览篇
  3. 图像引入篇