起因
最近在学习SVG
滤镜相关的知识,也在网上看到了很多酷炫的效果,本次给大家带来一个自己学习总结的模拟风的效果~
如果对SVG
滤镜感兴趣的朋友可以也去看看我之前的相关内容
正文开始!
本次想实现的效果是风吹过湖面产生波光粼粼的感觉!如果大家会webgl
可能更容易实现,但是普通的CSS
已经很难实现上图中的效果了,所以我们就要借助SVG
滤镜来帮助我们!
看过CSS 实现长虹玻璃(Fluted)效果的朋友们应该还记得,在长虹玻璃效果中最重要的一个滤镜feDisplacementMap
!
<feDisplacementMap>
是一个 SVG 滤镜原语,用于在图像上创建形变效果。它通过使用一个位移贴图来移动输入图像中的像素,从而产生扭曲、波纹或其他形变效果。
这次我们依然是利用feDisplacementMap
产生扭曲、波纹,来模拟风吹过的效果~但是本次核心的滤镜却是feTurbulence
!
feTurbulence
<feTurbulence>
是 SVG 中用于生成湍流效果的滤镜元素。它可以通过模拟噪声或随机模式来创建类似水面、云朵、风暴等自然现象的动态效果。常用于背景生成、纹理效果、噪声或其他有机形状的动画。
属性
-
type
这个属性决定了湍流的类型。可选值:
- "turbulence"(默认值):用于生成湍流噪声效果。
- "fractalNoise":用于生成分形噪声。
-
baseFrequency
这个属性控制湍流的“基础频率”,决定噪声的细节。值越大,湍流越细腻。通常用数组表示 [x, y],分别表示水平方向和垂直方向的频率。
-
numOctaves
这个属性控制噪声的级数或“音阶”,其值越大,生成的湍流效果越复杂。通常设置在 1 到 10 之间。
-
seed
用于控制随机数生成的种子,可以确保每次生成相同的噪声效果,保持一致性。是一个整数值。
-
stitchTiles
控制湍流是否在平铺时进行连接。stitchTiles 为 true 时,平铺的湍流效果会自动连接,以避免边界不连续的现象。
可以看到随着baseFrequency
接近1,整个图像的密度也在加大!我也写了一个Demo
大家可以自行体验,要注意的是feTurbulence
生成的时候开销比较大,拖拽完后可能需要等待一段时间~
简单的来说feTurbulence
会生成一种基于分形噪声 或 简单噪声的变体信息!单独使用feTurbulence
就是一种很克苏鲁风格的图片,此时如果我们搭配上feDisplacementMap
再来看看!
<div class="box">
</div>
<svg width="200" height="200">
<filter id='noise' x='0%' y='0%' width='100%' height='100%'>
<feTurbulence numOctaves="1" baseFrequency="0.05"/>
</filter>
<filter id='noise2' x='0%' y='0%' width='100%' height='100%'>
<feTurbulence numOctaves="1" baseFrequency="0.05"/>
<feDisplacementMap
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
</filter>
<rect x="0" y="0" width="100%" height="100%" filter="url(#noise)" fill="none">
</svg>
<div class="box">
</div>
.box {
width: 200px;
height: 200px;
background: white;
background: url("water.png") no-repeat center/cover;
}
.box:last-of-type {
filter: url(#noise2);
}
WOW,可以看到已经产生了一个非常好看扭曲效果!
feTurbulence + feDisplacementMap 动起来💃
目前扭曲效果已经实现了,接下来应该考虑如何让这个效果动起来产生动画效果!
我们已经知道feTurbulence
的baseFrequency
是用来控制湍流的“基础频率”feDisplacementMap
的scale
是用来定义位移的强度。较高的值会产生更大的位移效果!所以我们动态改变这几个值就能产生一个动画效果!
可以看到我们成功让这个效果动起来了,那么是不是我们就可以这样模拟风的效果了呢~很遗憾并不行!
我们想要的风是一直在吹的效果,也就是水波一直在往一个方向扭曲位移,但是单纯的使用animate
是不行的。
<feTurbulence id="turbulence" numOctaves="1" baseFrequency="0.05"/>
<feDisplacementMap
id="displacementMap"
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
<animate
xlink:href="#turbulence"
attributeName="baseFrequency"
dur="3s"
from="0.05"
to="0.086"
fill="freeze"
repeatCount="indefinite"/>
<animate
xlink:href="#displacementMap"
attributeName="scale"
dur="3s"
from="60"
to="120"
fill="freeze"
repeatCount="indefinite"/>
上述的动画效果总是从 60 -> 120 -> 60,而且由于feTurbulence
的特性,哪怕是新增0.01的变化,视觉上看也会非常明显就像水倒流了一样~所以我们需要换一个思路!
灵活运用feMerge/feImage
上述两个滤镜已经完成了核心的湍流效果,原理就是
SourceGraphic + feTurbulence生成得湍流图
配合feDisplacementMap
位移,所以我们其实只需要让feTurbulence生成得湍流图
位移也可以产生一个动画效果!
但是这里也会有一些问题,虽然滤镜本身可以通过result
和in
来互相配合,但是feImage
只接受外部链接!
feImage
<feImage>
是 SVG 滤镜中的一个元素,用于在滤镜操作中引入外部图像。它允许你将一个图像嵌入到滤镜效果中,可以将图像用作其他滤镜操作的输入,或者直接在应用滤镜的图形中显示图像。这个元素的主要功能是加载和显示外部图像,并可以与其他滤镜元素(如 、 等)结合使用,创建更复杂的效果。
主要属性
-
href
:用于指定要加载的外部图像的 URL。 支持外部图片文件(如 PNG、JPG)以及 Base64 编码的图像数据 URI。 必选属性。
-
href
:用于指定要加载的外部图像的 URL。 支持外部图片文件(如 PNG、JPG)以及 Base64 编码的图像数据 URI。 必选属性。
-
width, height
:定义图像的宽度和高度,单位可以是像素或百分比
-
preserveAspectRatio
:- 控制图像的长宽比如何保持。如果设置为 none,图像会被拉伸以适应指定的宽高。如果设置为其他值,图像会保持其原始比例。
- 常用值:none、xMidYMid(默认值,保持比例并居中)。
这里我们需要把feTurbulence
生成得底片当作外部图形传递给feImage
,需要用到一些JS
<svg id="svg" version='1.1' width="340" height="170" xmlns='http://www.w3.org/2000/svg'
color-interpolation-filters='sRGB'>
<defs>
<filter id="filter">
<feTurbulence
id="turbulence"
type="turbulence"
numOctaves="1"
seed="1"
baseFrequency="0.065 0.156">
</feTurbulence>
</filter>
</defs>
<rect width='340' height='170' filter="url(#filter)"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="filter_3">
<feImage class="feImage" href=""></feImage>
</filter>
</defs>
</svg>
const svgHtml = document.querySelector('#svg');
const svgString = svgHtml.outerHTML;
// 对 SVG 字符串进行 URL 编码
const encodedSvg = encodeURIComponent(svgString);
// 构建 data URI
const dataUri = `data:image/svg+xml,${encodedSvg}`;
const feImage = document.querySelectorAll('.feImage');
for (let image of feImage) image.setAttribute('href', dataUri);
核心是我们把SVG
进行URL
编码然后传给feImage
!现在看看效果如下图
介绍feImage
属性时可以看到其是有x,y
的也就是说可以对x,y
做平移动画!
<filter id='noise'>
<feImage
id="feImage"
class="feImage"
href=""
result="feImage1">
</feImage>
<feDisplacementMap
id="displacementMap"
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
<animate
class="animIn"
xlink:href="#feImage"
attributeName="x"
dur="5s"
from="0%"
to="100%"
fill="freeze"
repeatCount="indefinite"/>
</filter>
可以看到平移效果是有了,但是因为平移的不连贯导致看上去依然不是很像我们需要的风
feMerge
上面效果不连贯的原因就是因为feImage
本身和底背景是一样大的,所以feImage
平移之后就会露出部分圆图
熟悉CSS
的朋友应该知道这时候只需要设置repeat-x
属性并且在平移动画一段时间后重置平移距离即可!但是很遗憾我们的feImage
并不支持相关属性😥
所以我们只能考虑使用两个feImage
来达成这种无限循环的视觉效果!这里就引出了我们的feMerge
,因为SVG
滤镜本身是支持互相嵌套的也就是上一个滤镜的效果可以让下一个滤镜继续使用
<feTurbulence id="turbulence" numOctaves="1" baseFrequency="0.05"/>
👆 生成湍流效果
👇 使用feTurbulence生成的效果
<feDisplacementMap
id="displacementMap"
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
<feImage
id="feImage"
class="feImage"
href=""
result="feImage1">
</feImage>
👆 外部链接图片
👇 使用feImage的图片
<feDisplacementMap
id="displacementMap"
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
所以就需要我们feMerge
出场了!看名字相信大家也能看出来主要作用是用来合并滤镜效果让多个滤镜可以同时存在!
<feImage
id="feImage1"
class="feImage"
href=""
result="feImage1">
</feImage>
<feImage
id="feImage2"
class="feImage"
href=""
result="feImage2">
</feImage>
<feMerge>
<feMergeNode in="feImage1"></feMergeNode>
<feMergeNode in="feImage2"></feMergeNode>
</feMerge>
<feDisplacementMap
id="displacementMap"
in="SourceGraphic"
scale="60"
xChannelSelector="R"
yChannelSelector="B">
</feDisplacementMap>
<animate
class="animIn"
xlink:href="#feImage1"
attributeName="x"
dur="5s"
from="0"
to="200"
fill="freeze"
repeatCount="indefinite"/>
<animate
class="animIn"
xlink:href="#feImage2"
attributeName="x"
dur="5s"
from="-200"
to="0"
fill="freeze"
repeatCount="indefinite"/>
可以看到此时效果已经是无缝的了~
效果展示
SVG
滤镜的一个特点就是使用方便,上述我们一直用的是图片来做效果,只需要把图片替换成文字就可以实现我们文章开始时的效果啦!
结束语
最近在学习three.js
,前端真的很有意思呀!