SVG滤镜的艺术以及它为什么这么棒

如题所述

经过近20年的发展,如今的网页排版,以其高密度的显示以及OpenType功能的支持,离现实世界的印刷排版仅是一步之遥。但是平面设计还是有一个领域,我们还在不断地fall back到使用位图,而不是使用单纯的文本:字体显示,如说明性的、绚丽的、戏剧性的、俏皮的、试验性的或料想不到的艺术字母。
在HTML中显示文本的案例
我们可以从成千上万的Web字体中挑选,还可以为它们添加CSS效果,一些具有广泛浏览器支持(如投影和三维变换),一些可能是更试验性的(如 background-clip 背景裁剪和 text-stroke 文本描边),但这都是基本的。如果我们希望能够在我们的网站上显示真正优秀的字体,我们通常会选择把它作为图像嵌入。
在Web上使用图像的缺点是显而易见的:文件的大小,对于经常改变的或用户生成的内容缺乏可行性,可访问性,以及时间损耗等等。
所以如果我们能够为字母编辑样式,就像我们经常使用CSS修饰文本那样,岂不是很棒吗?为多个边界应用不同的颜色?添加内斜面、外斜面?添加图案、纹理和3D效果?给它一个通用的样式?使用多种颜色还有扭曲样式?给它一个繁琐的样式?
复杂的SVG滤镜:CSS
这其中的大部分都是已经可以实现的:关键是要释放SVG滤镜的魔力。SVG滤镜(包括CSS滤镜)通常被认为是一种通过模糊效果或颜色处理来处理位图的方式。但它们其实不只是这样。像CSS规则,SVG滤镜可以是一组添加在传统文本顶部的可视化图层。有了CSS的 filter 属性,这些效果可以在SVG之外使用,然后直接应用到HTML内容上。
说到CSS和SVG中的滤镜可能有一点疑问:SVG滤镜可用一个SVG filter 元素定义,而且可以在SVG文件中应用。CSS滤镜可以通过 filter 属性应用于任何HTML元素上。CSS滤镜如 blur , contrast 和 hue-rotate ,都是预定义的快捷方式,也是常用的SVG滤镜效果。除此之外, 规范 还允许我们引用SVG文件中用户自定义的滤镜。还有一点困惑的是专有的 -ms- filter 标签,在IE9中已经被废弃,在IE10发布时就已经被删除。
本文主要涉及的是第一种情况:嵌入在HTML页面中的SVG文件中使用的滤镜,但后面我们会试着把SVG滤镜应用于HTML内容。
这篇文章中的插图都是SVG滤镜效果应用于文本的示例。点击图片可查看原文(在现代支持SVG的浏览器中查看)。我把他们称为“复杂的”SVG过滤器,因为实际上这些滤镜是多种效果的组合,然后结合到一个输出的。尽管字母的外观已经有了显著的改变,实际上文本仍然是可抓取并且可获得的,可以选中并复制。因为SVG滤镜在所有的现代浏览器中都是支持的,这些效果可以在IE10以上的浏览器中显示。
理解SVG滤镜是有一定挑战性的。即使是像投影这样简单的效果都需要复杂并且详细的语法。一些滤镜,如 feColorMatrix 和 feComposite ,没有对数学和色彩理论有一个透彻的理解的话是很难掌握的。本文不是一篇学习SVG滤镜的教程。相反,我将介绍一组标准 构建模块 ,来完成一些效果,我会用尽量少的解释,重点在于记录完成这些效果的各个步骤。你看到的主要是关于如何完成,对于那些想要了解为什么的人,我在这篇文章的结尾处放了一个阅读列表。
构建滤镜
下面是一张复杂的SVG滤镜的构建图。滤镜输出的是风化的文本效果,我们将使用它作为示例,一步一步演练:
让我们把这个效果分解成几个部分:
绿色文字
红色投影
文字和投影使用一个透明间隙隔开
文字带有grungy和风化效果
我们的SVG滤镜是通过组合多个小模块构建而成的,也称为“滤镜原语”。每个模块都是由一组或更多原语构建而成的,然后再组合成统一的输出结果。下边的图片可以帮助你理解:

构建复杂滤镜的处理步骤,最好的说明图
添加滤镜
我们从一个包含空滤镜和文本的模板SVG文件开始:
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.filtered{
filter: url(#myfilter);

}
]]>
</style>

<filter id="myfilter">
<!-- filter stuff happening here -->
</filter>
</defs>

<g class="filtered">
<text x="0" y="200" transform="rotate(-12)">Petrol</text>
</g>
</svg>

滤镜元素
我们从 filter 标签元素开始,在其开始和结束标签中间,我们可以放置变换、颜色、位图操作等等所有规则。滤镜可以作为目标元素的属性应用,也可以通过CSS应用。目标元素通常是SVG中的元素,但是后边我们将了解另一个有趣的选择:把SVG滤镜应用于HTML元素。
几个用来控制 filter 元素的属性:
x 和 y 位置(默认 -10% );
width 和 height (默认 120% );
id 属性,对于后边的引用是必需的;
filterRes ,预定义解决方案(在“Filter Effects Module Level 1”规范中不建议使用);
相对单位(默认 objectBoundingBox )或绝对单位(默认 userSpaceOnUse) filterUnits 。
关于滤镜原语
正如我们已经知道的,滤镜原语是SVG滤镜的组成部分。任何一种效果,都至少包含一个原语。一个原语通常包含一个或两个输入( in , in2 ),以及一个输出(result )。原语输入包括模糊、移动、填充、结合或扭曲等等。
该规范允许我们采用滤镜元素的几个属性作为输入源。因为大多数的属性都不能跨浏览器运行,在这篇文章中我们会采用 SourceGraphic (未添加滤镜的元素,有颜色、描边、图案填充等等)和 SourceAlpha (alpha通道的不透明区域——即原图中填充黑色的部分),这两者都有非常好的浏览器支持。
如何加厚输入文本
我们要了解的第一个滤镜原语是 feMorphology ,一个用于把输入加厚(operator="dilate" )或变薄( operator="erode" )的原语——因此,非常适合用来创建轮廓和边界。
这是我们如何将 SourceAlpha 增粗4个像素:

增粗了4个像素的图
<feMorphology operator="dilate" radius="4" in="SourceAlpha" result="BEVEL_10" />

创建投影
下一步骤是在上一个原语的基础上创建一个3D的投影,结合 feConvolveMatrix。这个滤镜原语是最强大也最难以掌握的一个。它主要是帮助你创建自己的滤镜。总之,你会定义一个会根据其相邻像素的值变化的像素栅格(一个内核矩阵)。这样一来,你就可以创建自己的滤镜效果,如模糊、锐化滤镜,或投影。
这是 feConvolveMatrix 创建的一个 45deg 、 3px 的深度投影。 order 属性定义 width 和 height ,这样原语才知道是应用 3x3 的矩阵,还是 9x1 的矩阵:

使用 feConvolveMatrix 创建增粗的投影输入
<feConvolveMatrix order="3,3" kernelMatrix=
"1 0 0
0 1 0
0 0 1" in="BEVEL_10" result="BEVEL_20" />

考虑到IE11和Microsoft Edge无法处理大于 8x8 的矩阵,它们也无法很好地处理复杂矩阵,所以在部署这段代码之前先删除所有回车最好。
该原语同样可以应用于左、上、右、下各个方向。因为我们希望投影是往右下方的,我们需要修改结果。 targetX 和 targetY 这两个属性定义了效果的起点。可惜,IE对它们的解析不同于其它的浏览器。因此,要保持跨浏览器的兼容性,我们将使用另一个滤镜原语 feOffset 来处理。
OFFSETTING
顾名思义, feOffset 需要一个输入值,如下:
<feOffset dx="4" dy="4" in="BEVEL_20" result="BEVEL_30"/>

裁剪投影部分
feComposite 是为数不多的几个需要两个输入的滤镜原语之一。它运用了Porter-Duff合成来组合两张图像。 feComposite 可以用于掩蔽或裁剪元素。这是如何从feConvolveMatrix 输出的结果中减去 feMorphology 的输出。

从投影中裁剪掉第一个加粗的原语
<feComposite operator="out" in="BEVEL_20" in2="BEVEL_10" result="BEVEL_30"/>

为投影着色
这个过程包括两个步骤:
首先,我们使用 feFlood 创建一个着色区域。这个原语将会简单地在滤镜区域输出根据我们定义的颜色的矩形。
<feFlood flood-color="#582D1B" result="COLOR-red" />

然后我们再用一个 feComposite 裁剪掉 BEVEL_30 的透明部分:

为投影着色
<feComposite in="COLOR-red" in2="BEVEL_30" operator="in" result="BEVEL_40" />

将斜面和原图结合成一个输出
feMerge 可以把斜面和源一起输出:

斜面和原图混合成一个输出
<feMerge result="BEVEL_50">
<feMergeNode in="BEVEL_40" />
<feMergeNode in="SourceGraphic" />
</feMerge>

看起来像是我们期待的结果。让我们给它加一个风化的效果,看起来更逼真一些。
添加分形纹理
feTurbulence 是最好玩的原语之一。但是,它可能融化你的多核CPU,让你的风扇像波音747的涡轮喷气发动机那样旋转。所以,谨慎使用,尤其是在移动设备上,因为这个原语对渲染性能有非常坏的影响。
像 feFlood , feTurbulence 输出填充矩形,但使用的是杂乱的非结构化的纹理。
我们手头有几个值可用来改变纹理的质感和节奏。通过这种方式,我们可以创建像木头、沙子、水彩或破裂混凝土效果的表面。这些设置对滤镜的性能有直接的影响,所以测试要足够彻底。以下是如何创建一个类似描边画笔的纹理的代码:
<feTurbulence baseFrequency=".05,.004" width="200%" height="200%" top="-50%" type="fractalNoise" numOctaves="4" seed="0" result="FRACTAL-TEXTURE_10" />

默认情况下, feTurbulence 输出的是彩色纹理——不是我们想要的那个。我们需要一个灰度alpha图;多一点对比的话会更好。通过 feColorMatrix 来增加对比度,同时将它转换为灰度图:

最后,加上分形纹理的效果
<feColorMatrix type="matrix" values=
"0 0 0 0 0,
0 0 0 0 0,
0 0 0 0 0,
0 0 0 -1.2 1.1"
in="FRACTAL-TEXTURE_10" result="FRACTAL-TEXTURE_20" />

最后要做的就是将纹理alpha和文字组合,依然是使用我们的老朋友 feComposite:
<feComposite in="BEVEL_50" in2="FRACTAL-TEXTURE_20" operator="in"/>

终于完成啦O(∩_∩)O~
如何将SVG滤镜应用到SVG
以下是两种将SVG滤镜应用到SVG text 元素的方法:
通过CSS
.filtered {
filter: url(#filter);
}

通过属性
<text filter="url(#filter)">Some text</text>

将SVG滤镜应用到HTML内容
滤镜最鸡冻人心的特性之一是,它可以嵌入SVG,在SVG中定义滤镜,并使用CSS把它应用到任何HTML元素中:
filter: url(#mySVGfilter);

在写这篇文章的时候,Blink和WebKit都需要添加前缀,如下:
-webkit-filter: url(#mySVGfilter);

这在理论上听起来很容易,但实际中却是一种黑暗艺术orz:
WebKit、Firefox和Blink目前都支持SVG滤镜应用于HTML内容。IE和Microsoft Edge却会显示未添加滤镜的元素,所以要确保默认样式看起来也非常OK~
包含滤镜的SVG可能不会被设置为 display: none 。但是你可以自己设置visibility: hidden 。
有时候SVG的大小会直接影响应用的目标元素的多少。
我说过WebKit,Blink和Firefox理解这种语法吗?好吧,Safari(和它的小伙伴,Mobile Safari)是一个特例。你可以在Safari中跑一下这些demo,但是你很可能会抓狂。在写这篇文章的时候,我不建议在当前版本的Safari(8.0.6)中对HTML内容使用SVG滤镜。因为结果是不可预测的,技术并非刀枪不入。更糟糕的是,如果Safari因为某些原因无法渲染你的滤镜,它也不会显示目标HTML元素,噢噢噢噢噩梦:-(。基于经验法则,你增加你让Safari显示你的滤镜的机会,通过绝对定位和固定目标元素的大小。作为一个概念证明,我已经设置了一个 “流行的”滤镜效果,针对桌面版Safari 进行了优化。在Safari中,将 feImage 应用于HTML元素似乎是不可能的。
之前的DEMO,应用于HTML内容
在这些demo中,包裹元素都被设置为 contenteditable = "true" ,方便进行文本编辑。(请注意,这些demo都是实验,在Safari、IE或Edge中都是不能运行的。)
Image filled text
用图像填充文本
Extruded and filled with pattern
使用图案填充和投影
Extruded and illuminated
投影和发光
Grungy look with the help of fractal filters
分形滤镜帮助完成的grungy效果
feTurbulence to achieve spilled water effect
feTurbulence 完成溅水效果
Some pop-arty color effects
凸出的颜色效果
Sketchy style
手绘风格
自定义滤镜
根据其复杂程度,滤镜也可以是一个很复杂的东西。在制作滤镜的时候,你可以添加或移除规则、改变他们的顺序和值,但很快你就会变得混乱。这里有一些我自己写的规则,可以帮助我追踪发生的问题。因为人员和项目不同,在我看来逻辑和结构化的东西,在你看来可能是混乱和不知所云,所以采用并保留一下这些建议吧。
分组
我把滤镜原语根据它们自身的功能分成了几组——如:“border”、“fill”、“bevel”等等。在模块的开始和结束的地方,我会根据组名备注。
命名
良好的命名规则可以帮助你更好地组织滤镜,并且方便对原语内部和外部情况进行追踪。经过对 BEM-like schemas 的实验,我最终确定了一个非常简单的命名结构:
NAME-OF-GROUP_order-number

比如说,你可能使用像 BEVEL_10 , BEVEL_20 , OUTLINE_10 等等这样的命名。我从 10 开始,并使用 10 作为增量,方便调整原语的顺序,也方便在一组原语中间或开始的地方添加原语。我比较喜欢整块内容一起使用,因为它们能够帮我更快地扫描原内容。
保持声明输入和结果
尽管不是必要的,我通常都会声明一个“ in ”和“ result ”。(如果省略,原语的输出就默认是其继承者的输入)
一些构建模块
我们先看看单个技术能达到的效果。然后通过组合这些构建模块,我们可以创建新的复杂的滤镜效果。
文本描边
Outlined
<!-- 1. Thicken the input with feMorphology: -->

<feMorphology operator="dilate" radius="2"
in="SourceAlpha" result="thickened" />

<!-- 2. Cut off the SourceAlpha -->

<feComposite operator="out" in="SourceAlpha" in2="thickened" />

这个方法并不能保证结果是好看的。尤其是你将 dilate 与较大的 radius 值结合的时候,结果可能比通过 stroke-width 创建的几何体糟糕一些。根据不同的情况,比较好的选择是将文本存储在一个符号元素中,然后在需要的时候通过 use 插入,再通过CSS的 stroke-width 属性将其加厚。注意, stroke-width 不能应用于HTML内容。
撕裂效果
Torn Out
<!-- 1. create an feTurbulence fractal fill -->

<feTurbulence result="TURBULENCE" baseFrequency="0.08"
numOctaves="1" seed="1" />

<!-- 2. create a displacement map that takes the fractal fill as an input to distort the target: -->

<feDisplacementMap in="SourceGraphic" in2="TURBULENCE" scale="9" />

颜色填充
Colored
<!-- 1. Create a colored filled area -->

<feFlood flood-color="#F79308" result="COLOR" />

<!-- 2. Cut off the SourceAlpha -->

<feComposite operator="in" in="COLOR" in2="SourceAlpha" />

有一点需要提到的是,除了 feFlood , feColorMatrix 是另一种能够改变原输入颜色的方法,尽管它本身的概念比较难以理解。
OFFSETTING
Off Set.
<!-- Offset the input graphic by the amount defined in its "dx" and "dy" attributes: -->

<feOffset in="SourceGraphic" dx="10" dy="10" />

投影
Extruded
<!-- Define a convolve matrix that applies a bevel. -->
<!-- Order defines the depth of the extrusion; angle is defined by the position of "1" in the matrix. Here we see a 45-degree, 4-pixel deep extrusion: -->
<feConvolveMatrix order="4,4"
kernelMatrix="
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1" in="SourceAlpha" result="BEVEL" />
<!-- offset extrusion: -->
<feOffset dx="2" dy ="2" in="BEVEL" result="OFFSET" />
<!-- merge offset with Source: -->
<feMerge>
<feMergeNode in="OFFSET" />
<feMergeNode in="SourceGraphic" />
</feMerge>
温馨提示:答案为网友推荐,仅供参考