profile picture of Olivier Larose

Olivier Larose

January 18, 2024

/

Advanced

/

Medium

Text Gooey

How to Make a Text Gooey Morph Effect Using SVG Filters

How to Make a Text Gooey Morph Effect Using SVG filters feGaussianBlur and feColorMatrix. All inside a Next.js application and animated with Framer Motion. Inspired by: https://codepen.io/supah/pen/abVGjVq

Live DemoSource code
background video

To find out more on how to make this animation, you can check these links, they greatly helped me understand this animation:

This animation presents multiple interesting animation techniques, mainly on knowing how to work with SVG filters and also reading and animating along an SVG path.

Initializing the project

Let's start the project by creating a Next.js application. We can do that by running npx create-next-app@latest client inside of a terminal.

We can delete everything in the page.js , global.css and page.module.css and add our own HTML and CSS, to start with a nice blank application.

  • We will use Sass for the stylesheets, so we can run npm i sass.
  • We will use Framer Motion for the animation, so we can run npm i framer-motion.

Overal concept

Here's how this animation is made:

  • Render all the numbers as SVG path
  • Render 30 circles as SVG circle
  • Animate the circles along the number's path
  • Add a feGaussianBlur and a feColorMatrix filter on the circles.

Rendering the numbers

The first step is to render all the numbers as SVG path.

The goal here is to draw an SVG path for each numbers you need. You can that in either Figma or Illustrator and use this tool to visualize your SVG code.

Here I put the d attribute of each of my numbers inside an array that I can then loop to render all of them.

Array of d attributes

1

const numbers = [

2

"M87.9,79.2c1.1-0.4,53.7-39.2,54.9-39.1v180.5",

3

"M81.7,85.7c-1.4-67,112.3-55.1,90.2,11.6c-12.6,32-70.6,83.7-88.8,113.7h105.8",

4

"M74.8,178.5c3,39.4,63.9,46.7,88.6,23.7c34.3-35.1,5.4-75.8-41.7-77c29.9,5.5,68.7-43.1,36.5-73.7 c-23.4-21.5-76.5-11.1-78.6,25",

5

"M161.9,220.8 161.9,41 72.6,170.9 208.2,170.9",

6

"M183.2,43.7H92.1l-10,88.3c0,0,18.3-21.9,51-21.9s49.4,32.6,49.4,48.2c0,22.2-9.5,57-52.5,57s-51.4-36.7-51.4-36.7"

7

]

And here I render all of them and dynamically add them inside an array:

page.js

1

export default function Home() {

2

const paths = useRef([]);

3

return (

4

<main className={styles.main}>

5

<svg viewBox="0 0 256 256">

6

<g>

7

{

8

numbers.map((path, i) => {

9

return <path key={`p_${i}`} ref={ref => paths.current[i] = ref} d={path}/>

10

})

11

}

12

</g>

13

</svg>

14

</main>

15

)

16

}

Rendering the circles

To create the Gooey effect, I'll move a bunch of circles one after the other along the line of each number's SVG path.

The first step to do that is render 30 circles:

page.js

1

export default function Home() {

2

3

...

4

const circles = useRef([]);

5

const nbOfCircles = 30;

6

const radius = 20;

7

8

return (

9

<main className={styles.main}>

10

<svg viewBox="0 0 256 256">

11

...

12

<g>

13

{

14

[...Array(nbOfCircles)].map( (_, i) => {

15

return <circle key={`c_${i}`} ref={ref => circles.current[i] = ref} cx="128" cy="128" r={radius} />

16

})

17

}

18

</g>

19

</svg>

20

</main>

21

)

22

}

Each circle has a cx and cy value of 128, which is the center point of the SVG, which has a bounding box of 0 0 256 256.

Animating the circles

To control the animation, we can render a bunch of paragraphs that will toggle a state on click:

page.js

1

const [index, setIndex] = useState(0);

2

3

return (

4

<main className={styles.main}>

5

<div className={styles.numbers}>

6

{

7

numbers.map((_, i) => {

8

return <p style={{color: i == index ? "red" : "black"}} onClick={() => {setIndex(i)}}>{i + 1}</p>

9

})

10

}

11

</div>

12

...

13

) </main>

14

Here when the index gets changed, we take each circle and animate them along the path of each number:

page.js

1

const [index, setIndex] = useState(0);

2

const circles = useRef([]);

3

const paths = useRef([]);

4

const nbOfCircles = 30;

5

6

useEffect( () => {

7

const length = paths.current[index].getTotalLength();

8

const step = length / nbOfCircles;

9

10

circles.current.forEach((circle, i) => {

11

const { x, y } = paths.current[index].getPointAtLength(i * step);

12

animate(circle,

13

{ cx: x, cy: y},

14

{ delay: i * 0.025, ease: "easeOut"}

15

)

16

})

17

}, [index])
  • Line 7: Calculate the path length of the current selected number.
  • Line 8: Calculate a step depending on the length of that path and the number of circles.
  • Line 10-16: Animate each circle to a specific point along the SVG.

We should have something like this:

Adding the filters

Two filters are needed to create the gooey effect:

  • feGaussianBlur: blurs the input by the amount specified in stdDeviation.
  • feColorMatrix: changes the color of the input based on a transformation matrix.

page.js

1

<defs>

2

<filter id="filter">

3

<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />

4

<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 25 -15" result="filter" />

5

</filter>

6

</defs>

The feGaussianBlur is pretty straight forward and doesn't need much explanation. It's a standard gaussian blur that's adjusted with the stdDeviation.

page.js

1

<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />

However, the feColorMatrix is quite tricky and necessary to understand how the blur gooey effect is created. I highly suggest reading this in order to understand.

First of all, the identity matrix, meaning when there are no transformations applied by the filter looks like this:

Identity matrix

1

R G B A W

2

R' | 1 0 0 0 0 |

3

G' | 0 1 0 0 0 |

4

B' | 0 0 1 0 0 |

5

A' | 0 0 0 1 0 |

Then what we do is modify that matrix in the alpha channel only.

Modified matrix

1

R G B A W

2

R' | 1 0 0 0 0 |

3

G' | 0 1 0 0 0 |

4

B' | 0 0 1 0 0 |

5

A' | 0 0 0 25 -15 |

And here's how each number of the alpha channel of the matrix is added to create the new alpha:

Modified matrix

1

A' = a1*R + a2*G + a3*B + a4*A + a5

Here's a little explanation:

  • Since the feGaussianBlur comes and blur the circles, the alpha of each pixel can now be all the numbers between 0 and 1 instead of just being either 0 or 1.
  • The feColorMatrix will be applied after the feGaussianBlur. So the alpha of each pixel that is now between 0 and 1 will be multiplied by the new feColorMatrix alpha values (current_alpha * 25 - 15) and note that the final A' will be clamped between 0 and 1.
  • So it's the combination of both these filters that creates the gooey effect.

So in the end we would have something like this:

page.js

1

<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 25 -15" result="filter" />

Wrapping up

That's it for this tutorial!

It was very interesting for me to study how we can animate things along the line of an SVG and also how we can apply SVG's filter to create awesome effects! Hope you learned something.

-Oli

Related Animations

image

June 2, 2024

Mouse Image Distortion

A website animation featuring an image distortion in a curved, using the sin function, React, React Three Fiber and Framer Motion

image

May 4, 2024

Paint Reveal

A website tutorial on making a paint reveal / erasing effect using the destination out blend mode of the canvas API, made with React and Next.js

image

March 8, 2024

Blend Mode Cursor

A website tutorial featuring a moving cursor on mouse move, colored with CSS blend mode difference, made with React and GSAP. Inspired by https://trionn.com/