Search code examples
svgd3.jssvg-filters

Seamless Waving Effect in svg


Is there a way to achieve a perfect seamless waving effect for the whole of a svg. I tried approaching the problem by animating the baseFrequency of feTurbulencefilter but it is not giving me what I need.

const lower = 0;
const upper = 0.008;
const step = 0.0001;
const time = 5000;

function animateBaseFrequency() {
    let direction = 1; // 1 for incrementing, -1 for decrementing
    let x = lower;

    d3.select('#fltOne')
        .transition()
        .duration(time)
        .ease(d3.easeLinear)
        .tween('x', function() {
            return function(t) {
                x = x + step * direction;
                if (x >= upper || x <= lower) {
                    direction *= -1; // Reverse direction at upper and lower bounds
                }

                d3.select(this).attr('baseFrequency', x);
            };
        }).on('end', animateBaseFrequency);
}

animateBaseFrequency();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
    <link rel="stylesheet" href="style.css">
    <div id="container" class="svg-container">
        <svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg">
            <defs id="pattern">
                <pattern id="af" width="100%" height="100%" patternContentUnits="objectBoundingBox">
                    <image xlink:href="https://raw.githubusercontent.com/d3/d3-logo/master/d3.svg"
                        preserveAspectRatio="xMidYMid meet" width="1" height="1"></image>
                </pattern>
                <pattern patternContentUnits="userSpaceOnUse" id="main">
                    <rect class="patRect" width="400" height="300" fill="url(#af)"></rect>
                </pattern>
                <filter id="filt">
                    <feTurbulence result="TURBULENCE" numOctaves="1" seed="1" baseFrequency="0" stitchTiles="noStitch"
                        id="fltOne"></feTurbulence>
                    <feDisplacementMap in="SourceGraphic" in2="TURBULENCE" scale="30" result="dist" id="fltTwo">
                    </feDisplacementMap>
                </filter>
            </defs>
            <rect class="rectOne" x="100" y="100" width="400" height="300" fill="url(#af)"
                style="filter: url(&quot;#filt&quot;);"></rect>
        </svg>
    </div>
    <!--d3 script-->
    <script src="prod.js" defer></script>
</body>

</html>


Solution

  • How about this:

    • use stitchTiles="stitch" on the turbulence so that a seamlessly repeatable tile is produced
    • repeat the tubulence tile two times with a double-wide <feTile>
    • animate the x position of that pattern repeatedly for the width of the turbulence tile
    • use the result as input for the displacement map

    enter image description here

    Note that the whole filter must have sufficient width to contain the complete repeated tile before the offset is applied, otherwise <feTile> will be cut of at the border of the filter region.

    In the picture, the green dashed line is the extent of the image the filter is applied to. The blue line is the filter effect region. Every filter effect is clipped to this rectangle. Its size depends on the values of the attributes filterUnits, x, y, width, height. In the line below, default values are shown in grey.

    The size of each filter primitive, the filter primitive subregion is given with its own x, y, width, height values. But the values are not interpreted in multiples of the image size, like the filter effect region, but in the coordinate system of the image, as prescribed by the attribute filterPrimitiveUnits="userSpaceOnUse".

    The tiled pattern fills the filter effects region. Would that be smaller, it would be cut of at its border, regardless what the size of the feTile primitive says.

    The last line of pictures shows stages of the animation. Note how the tiled pattern is cut of at the left side.

    <svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg">
        <defs id="pattern">
            <filter id="filt" width="2">
                <feTurbulence numOctaves="1" seed="1" baseFrequency="0.008"
                              stitchTiles="stitch"
                              id="fltOne" width="400" height="400">
                </feTurbulence>
                <feTile width="800" height="400">
                </feTile>
                <feOffset result="TURBULENCE" dx="0">
                    <animate attributeName="dx" from="-400" to="0"
                             begin="0s" dur="2s" repeatCount="indefinite" />
    
                </feOffset>
                <feDisplacementMap in="SourceGraphic" in2="TURBULENCE" scale="30" result="dist" id="fltTwo">
                </feDisplacementMap>
            </filter>
        </defs>
        <image xlink:href="https://raw.githubusercontent.com/d3/d3-logo/master/d3.svg" x="100" y="100" width="400" height="300" style="filter: url(#filt);" />
    </svg>