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:
- Setting up text elements with refs
- Implementing the morphing animation
- Managing the cooldown period between transitions
- 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:
- Calculates the time elapsed since the last frame
- Updates the cooldown timer
- Determines whether to start a new morph or continue the cooldown
- 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:
- Preserving the red, green, and blue channels (first three rows)
- Manipulating the alpha channel (last row)
- 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.