0

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>

1
  • Instead of direction you may try to use Math.sin( x + step ) or Math.cos. In this case you don't need to reverse the direction since the sin function goes from -1 to 1
    – enxaneta
    Commented Jul 27, 2023 at 16:53

1 Answer 1

4

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>

3
  • I think you will find <animate from="-20" to="0" ... />. Otherwise the right border of the tiled pattern ends up at the left side of the picture, not covering it at the beginning of each animation loop.
    – ccprog
    Commented Jul 28, 2023 at 16:55
  • The problem is the missing width attribute on the filter. I've added further illustrations and explanations to my answer, as sizing filter regions is a complex endeavour.
    – ccprog
    Commented Jul 28, 2023 at 18:05
  • Thanks again. I will settle with scale for now.
    – smpa01
    Commented Jul 28, 2023 at 19:18

Not the answer you're looking for? Browse other questions tagged or ask your own question.