• 作者:老汪软件技巧
  • 发表时间:2024-11-15 07:02
  • 浏览量:

起因

最近在学习SVG滤镜相关的知识,也在网上看到了很多酷炫的效果,本次给大家带来一个自己学习总结的模拟风的效果~

如果对SVG滤镜感兴趣的朋友可以也去看看我之前的相关内容

正文开始!

本次想实现的效果是风吹过湖面产生波光粼粼的感觉!如果大家会webgl可能更容易实现,但是普通的CSS已经很难实现上图中的效果了,所以我们就要借助SVG滤镜来帮助我们!

看过CSS 实现长虹玻璃(Fluted)效果的朋友们应该还记得,在长虹玻璃效果中最重要的一个滤镜feDisplacementMap!

是一个 SVG 滤镜原语,用于在图像上创建形变效果。它通过使用一个位移贴图来移动输入图像中的像素,从而产生扭曲、波纹或其他形变效果。

这次我们依然是利用feDisplacementMap产生扭曲、波纹,来模拟风吹过的效果~但是本次核心的滤镜却是feTurbulence!

feTurbulence

是 SVG 中用于生成湍流效果的滤镜元素。它可以通过模拟噪声或随机模式来创建类似水面、云朵、风暴等自然现象的动态效果。常用于背景生成、纹理效果、噪声或其他有机形状的动画。

属性

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);
    }

05.png

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

是 SVG 滤镜中的一个元素,用于在滤镜操作中引入外部图像。它允许你将一个图像嵌入到滤镜效果中,可以将图像用作其他滤镜操作的输入,或者直接在应用滤镜的图形中显示图像。这个元素的主要功能是加载和显示外部图像,并可以与其他滤镜元素(如 、 等)结合使用,创建更复杂的效果。

主要属性

href:

用于指定要加载的外部图像的 URL。支持外部图片文件(如 PNG、JPG)以及 Base64 编码的图像数据 URI。必选属性。

href:

用于指定要加载的外部图像的 URL。支持外部图片文件(如 PNG、JPG)以及 Base64 编码的图像数据 URI。必选属性。

width, height:

定义图像的宽度和高度,单位可以是像素或百分比

preserveAspectRatio:

这里我们需要把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>

_css滤镜特效属于css中_ps滤镜风没有拉丝效果

    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!现在看看效果如下图

06.png

介绍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,前端真的很有意思呀!