Search code examples
csssvgcss-animationssvg-filters

How to create SVG Gooey effect


I am trying to recreate something like this.

This is what I have tried

// targeting the svg itself
const svg = document.querySelector("svg");

// variable for the namespace 
const svgns = "http://www.w3.org/2000/svg"

//vboxDim

var vboxW = 200;
var vboxH = 200;

//assigning svg element attribute 
svg.setAttribute('class', 'layer1');
svg.setAttribute('xmlns', svgns);
svg.setAttribute('viewBox', `0 0 ${vboxW} ${vboxH}`);

//make background
var fill1 = 'black';

let bg = document.createElementNS(svgns, 'rect');
bg.setAttribute('class', 'bg');
bg.setAttribute('id', 'bg');
bg.setAttribute("width", `${vboxW}`);
bg.setAttribute("height", `${vboxH}`);
bg.setAttribute("fill", fill1);

svg.appendChild(bg);

var color = ['white', 'white']
var r = 20;
var cx = (vboxH / 2);
var cy = (vboxW / 2) - 20;

for (var i = 0; i < 2; i++) {
    let circ = document.createElementNS(svgns, 'circle');
    circ.setAttribute('class', 'crcl' + i);
    circ.setAttribute('id', 'crcl' + i);
    circ.setAttribute("cx", `${cx}`);
    circ.setAttribute("cy", `${cy}`);
    circ.setAttribute("r", r);
    circ.setAttribute("fill", color[i]);
    svg.appendChild(circ);
    r = r - 12;
    cy = cy + .5;


}
.crcl0 {
    filter: url(#goo);
}

.crcl1 {
    filter: url(#goo);
    animation: move 5s linear infinite alternate forwards;
}

@keyframes move {
    0% {
        transform: translate(-80px, 0);
    }
    100% {
        transform: translate(+80px, 0);
    }
<!DOCTYPE html>
<html>

<body>
    <link rel="stylesheet" href="style.css"></link>
    <svg>
      <defs>
    <filter id="goo">
      <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
      <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -10" result="goo" />
      <feBlend in="SourceGraphic" in2="goo" />
    </filter>
  </defs>
  
    <script href="index.js"></script>
</svg>

</body>

</html>

As you can see that it is not creating the desired effect at all. I can't seem to understand why is that. I know in creating a gooey effect the background color must be contrasting which I believe is the case above.

Can someone please help me point me to the correct direction as to what am I doing wrong here? Also, I need to admit that I don't understand the math behind the svg filter used here. Does that need to be manipulated to achieve the desired effect?


Solution

  • The filter must affect both shapes at the same time so that they are part of the same "SourceGraphic". To do so, you can use a <g> element and add the filter on it:

    // targeting the svg itself
    const svg = document.querySelector("svg");
    
    // variable for the namespace 
    const svgns = "http://www.w3.org/2000/svg"
    
    //vboxDim
    
    var vboxW = 200;
    var vboxH = 200;
    
    //assigning svg element attribute 
    svg.setAttribute('class', 'layer1');
    svg.setAttribute('xmlns', svgns);
    svg.setAttribute('viewBox', `0 0 ${vboxW} ${vboxH}`);
    
    //make background
    var fill1 = 'black';
    
    let bg = document.createElementNS(svgns, 'rect');
    bg.setAttribute('class', 'bg');
    bg.setAttribute('id', 'bg');
    bg.setAttribute("width", `${vboxW}`);
    bg.setAttribute("height", `${vboxH}`);
    bg.setAttribute("fill", fill1);
    
    svg.appendChild(bg);
    
    var group = document.createElementNS(svgns, "g");
    group.classList.add("gooey");
    svg.appendChild(group);
    
    var color = ['white', 'white']
    var r = 20;
    var cx = (vboxH / 2);
    var cy = (vboxW / 2) - 20;
    
    for (var i = 0; i < 2; i++) {
      let circ = document.createElementNS(svgns, 'circle');
      circ.setAttribute('class', 'crcl' + i);
      circ.setAttribute('id', 'crcl' + i);
      circ.setAttribute("cx", `${cx}`);
      circ.setAttribute("cy", `${cy}`);
      circ.setAttribute("r", r);
      circ.setAttribute("fill", color[i]);
      group.appendChild(circ);
      r = r - 12;
      cy = cy + .5;
    }
    .gooey {
      filter: url(#goo);
    }
    
    .crcl1 {
      animation: move 5s linear infinite alternate forwards;
    }
    
    @keyframes move {
      0% {
        transform: translate(-80px, 0);
      }
      100% {
        transform: translate(+80px, 0);
      }
    }
    <!DOCTYPE html>
    <html>
    
    <body>
      <link rel="stylesheet" href="style.css"></link>
      <svg>
          <defs>
        <filter id="goo">
          <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
          <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -10" result="goo" />
          <feBlend in="SourceGraphic" in2="goo" />
        </filter>
      </defs>
      
        <script href="index.js"></script>
    </svg>
    
    </body>
    
    </html>