Home
Gooey Text Morphing

November 20, 2024

The Gooey Text Morphing component creates a smooth, fluid transition between different text phrases using blur effects and opacity changes. Here's how it's built:

  1. Setting up text elements with refs
  2. Implementing the morphing animation
  3. Managing the cooldown period between transitions
  4. Applying SVG filters for the gooey effect

This approach results in a visually appealing text transition with a unique gooey appearance.

Text Setup and References

We use React's useRef hook to create references to two text elements that will be morphed:

const text1Ref = useRef<HTMLSpanElement>(null);
const text2Ref = useRef<HTMLSpanElement>(null);

These refs will allow us to directly manipulate the DOM elements for the text transition effect.

Here we control the duration of the morphing animation and the cooldown between transitions:

const MORPH_TIME = 1;
const COOLDOWN_TIME = 0.25;

MORPH_TIME determines how long each transition takes, while COOLDOWN_TIME sets the pause between transitions. These values are in seconds.

const TEXTS = ["Design", "Engineering", "Is", "Awesome"];

This array contains the words that will be cycled through in the animation. The component will transition between these words in sequence, creating the morphing effect.

Animation Logic

The core animation logic is implemented within a useEffect hook. Here's a breakdown of the key parts:

useEffect(() => {
  let textIndex = TEXTS.length - 1;
  let time = new Date();
  let morph = 0;
  let cooldown = cooldownTime;
 
  function animate() {
    requestAnimationFrame(animate);
 
    const newTime = new Date();
    const shouldIncrementIndex = cooldown > 0;
    const dt = (newTime.getTime() - time.getTime()) / 1000;
    time = newTime;
 
    cooldown -= dt;
 
    if (cooldown <= 0) {
      if (shouldIncrementIndex) {
        textIndex++;
      }
      doMorph();
    } else {
      doCooldown();
    }
  }
 
  animate();
 
  return () => {};
}, []);

This setup creates an animation loop using requestAnimationFrame. It manages the timing for morphing and cooldown periods.

Animation Loop

The animate function creates a continuous loop using requestAnimationFrame:

function animate() {
  requestAnimationFrame(animate);
 
  const newTime = new Date();
  const shouldIncrementIndex = cooldown > 0;
  const dt = (newTime.getTime() - time.getTime()) / 1000;
  time = newTime;
 
  cooldown -= dt;
 
  if (cooldown <= 0) {
    if (shouldIncrementIndex) {
      textIndex++;
    }
    doMorph();
  } else {
    doCooldown();
  }
}

This function:

  1. Calculates the time elapsed since the last frame
  2. Updates the cooldown timer
  3. Determines whether to start a new morph or continue the cooldown
  4. Calls either doMorph() or doCooldown() based on the current state

The loop ensures smooth transitions between text phrases and manages the timing of the morphing effect.

Morphing Function

The doMorph function handles the transition between texts:

function doMorph() {
  morph -= cooldown;
  cooldown = 0;
 
  let fraction = morph / MORPH_TIME;
 
  if (fraction > 1) {
    cooldown = COOLDOWN_TIME;
    fraction = 1;
  }
 
  setMorph(fraction);
}

The setMorph function applies the visual changes:

function setMorph(fraction: number) {
  if (text1Ref.current && text2Ref.current) {
    text2Ref.current.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
    text2Ref.current.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;
 
    fraction = 1 - fraction;
 
    text1Ref.current.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
    text1Ref.current.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;
  }
}

This function applies blur and opacity changes to create the morphing effect.

Cooldown Management

The doCooldown function resets the styles after a morph:

function doCooldown() {
  morph = 0;
 
  if (text1Ref.current && text2Ref.current) {
    text2Ref.current.style.filter = "";
    text2Ref.current.style.opacity = "100%";
 
    text1Ref.current.style.filter = "";
    text1Ref.current.style.opacity = "0%";
  }
}
SVG Filter for Gooey Effect

The gooey effect is achieved using an SVG filter:

<svg className="absolute h-0 w-0" aria-hidden="true" focusable="false">
  <defs>
    <filter id="threshold">
      <feColorMatrix
        in="SourceGraphic"
        type="matrix"
        values="1 0 0 0 0
                0 1 0 0 0
                0 0 1 0 0
                0 0 0 255 -140"
      />
    </filter>
  </defs>
</svg>

This filter is applied to the main container:

<div
  className="absolute inset-0 flex items-center justify-center"
  style={{ filter: "url(#threshold)" }}
>
  <span
    ref={text1Ref}
    className="absolute inline-block select-none text-center text-6xl md:text-[60pt]"
  ></span>
  <span
    ref={text2Ref}
    className="absolute inline-block select-none text-center text-6xl md:text-[60pt]"
  ></span>
</div>

The two span elements are the core of our text morphing effect. Each span is referenced using text1Ref and text2Ref respectively, allowing us to manipulate their content and styles in our animation functions.

Understanding the SVG Filter

The SVG filter uses a feColorMatrix to create the gooey effect:

<filter id="threshold">
  <feColorMatrix
    in="SourceGraphic"
    type="matrix"
    values="1 0 0 0 0
            0 1 0 0 0
            0 0 1 0 0
            0 0 0 255 -140"
  />
</filter>

This filter works by:

  1. Preserving the red, green, and blue channels (first three rows)
  2. Manipulating the alpha channel (last row)
  3. The value -140 in the last row creates a threshold effect, causing partially transparent areas to become either fully opaque or fully transparent

This threshold effect, combined with the blur applied in the JavaScript, creates the distinctive gooey appearance during text transitions.

Conclusion

The Gooey Text Morphing component demonstrates an advanced text animation technique using React hooks, CSS animations, and SVG filters. By carefully managing the opacity and blur of two text elements, and applying a custom SVG filter, we achieve a unique and visually appealing gooey transition effect between words.