Gooey Text
A smooth text morphing animation with a gooey blur effect that transitions between multiple texts.
Loading...
import { GooeyText } from "@/components/core/gooey-text-morphing";
export default function GooeyTextExample() {
return (
<div className="h-[200px] flex items-center justify-center">
<GooeyText
texts={["Open", "Source", "Make", "Your", "Life", "Better"]}
morphTime={1}
cooldownTime={0.25}
className="font-bold"
/>
</div>
);
}
Installation
Copy and paste the following code into your project:
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
interface GooeyTextProps {
texts: string[];
morphTime?: number;
cooldownTime?: number;
className?: string;
textClassName?: string;
}
export function GooeyText({
texts,
morphTime = 1,
cooldownTime = 0.25,
className,
textClassName
}: GooeyTextProps) {
const text1Ref = React.useRef<HTMLSpanElement>(null);
const text2Ref = React.useRef<HTMLSpanElement>(null);
React.useEffect(() => {
let textIndex = texts.length - 1;
let time = new Date();
let morph = 0;
let cooldown = cooldownTime;
const 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}%`;
}
};
const 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%";
}
};
const doMorph = () => {
morph -= cooldown;
cooldown = 0;
let fraction = morph / morphTime;
if (fraction > 1) {
cooldown = cooldownTime;
fraction = 1;
}
setMorph(fraction);
};
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 = (textIndex + 1) % texts.length;
if (text1Ref.current && text2Ref.current) {
text1Ref.current.textContent = texts[textIndex % texts.length];
text2Ref.current.textContent = texts[(textIndex + 1) % texts.length];
}
}
doMorph();
} else {
doCooldown();
}
}
animate();
return () => {
// Cleanup function if needed
};
}, [texts, morphTime, cooldownTime]);
return (
<div className={cn("relative", className)}>
<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>
<div
className="flex items-center justify-center"
style={{ filter: "url(#threshold)" }}
>
<span
ref={text1Ref}
className={cn(
"absolute inline-block select-none text-center text-6xl md:text-[60pt]",
"text-foreground",
textClassName
)}
/>
<span
ref={text2Ref}
className={cn(
"absolute inline-block select-none text-center text-6xl md:text-[60pt]",
"text-foreground",
textClassName
)}
/>
</div>
</div>
);
}Update the import paths to match your project setup.
Usage
import { GooeyText } from "@/components/core/gooey-text-morphing";
export default function GooeyTextExample() {
return (
<div className="h-[200px] flex items-center justify-center">
<GooeyText
texts={["Open", "Source", "Make", "Your", "Life", "Better"]}
morphTime={1}
cooldownTime={0.25}
className="font-bold"
/>
</div>
);
}
Props
| Prop | Type | Default |
|---|---|---|
texts | string[] | - |
morphTime? | number | 1 |
cooldownTime? | number | 0.25 |
className? | string | - |
textClassName? | string | - |