SVG filter Svelte Transition

sveltetransition

The Result

the-result

Break it down

I'm using a combination of SVG filters <feTurbulence>, <feDisplacementMap> to achieve this effect.

Svelte REPL

feTurbulence

To use this SVG filter, I first created a Svelte component for the filter.

The idea is to add this render this filter whenever I want to transition, and update the scale and frequency during the transition

<script>
export let baseFrequency = 0;
export let scale = 0;
export let id;
</script>
<svg height="0" width="0">
<filter {id}>
<feTurbulence
type="turbulence" numOctaves="2"
{baseFrequency}
result="turbulence" />
<feDisplacementMap
in2="turbulence" in="SourceGraphic"
xChannelSelector="R" yChannelSelector="G"
{scale} />
</filter>
</svg>

As I'm going to have multiple instance of the SvgFilter, and I don't want the filter id to be conflict with each other, I generated the id everytime there's a new instance of SvgFilter

<script context="module">
let _idx = 0;
</script>
<script>
// ...
export let id = `wave-${_idx++}`;
</script>

I created the SvgFilter component as the transition starts and remove it as the transition ends

import SvgFilter from './SvgFilter.svelte';

function wavy(node, params = {}) {
  let svgFilter;
  		
  node.addEventListener('introstart', createIntro);

  // create SvgFilter
  function createIntro() {
    svgFilter = new SvgFilter({ target: document.body });
    node.style.filter = `url(#${svgFilter.id})`;
    node.removeEventListener('introstart', createIntro);
    node.addEventListener('introend', cleanup);
  }

  function cleanup() {
    svgFilter.$destroy();
    node.removeEventListener('introend', cleanup);
  }
  // ...
}

And as the transition ticks by, I set the scale of the SVG filter based on the time, t

function wavy(node, params = {}) {
  let svgFilter;

  // ...
  return {
    ...params,
    tick(t) {
      // t     0   -> 1
      // scale 100 -> 0
      if (svgFilter) 
        svgFilter.$set({
          scale: (1 - t) * 100,
          baseFrequency: 0.55,
        });
    }
  }
}

Add some fade out using the opacity

function wavy(node, params = {}) {
  let svgFilter;

  // ...
  return {
    ...params,
    css(t) {
      // only fade out in the last quarter of the time
      // t       1 -> 0.25 -> 0
      // opactiy 1 -> 1    -> 0
      return t < 0.25 ? `opacity: ${t * 4}` : 'opacity: 1';
    }
    tick(t) {
      // t     0   -> 1
      // scale 100 -> 0
      if (svgFilter) 
        svgFilter.$set({
          scale: (1 - t) * 100,
          baseFrequency: 0.55,
        });
    }
  }
}

Final REPL