Gooey Words
The Gooey Words component adds a delightful splash of motion and creativity to your UI. Watch as vibrant, animated text words like "GLIDE", "SHINE", and "CLOUD" morph and bounce in a mesmerizing gooey effect using Framer Motion. Perfect for hero sections, splash pages, or just to add some playful flair to your design. It's responsive, interactive, and ridiculously fun — because text should never be boring. Bring your interface to life with bouncy vibes and smooth animations
Preview
Follow below steps 👇🏻
Install dependencies
1npm i framer-motion
Component
Create a file gooey-words.tsx in your components folder and paste this code
1'use client';23import { animate, AnimatePresence, motion } from 'framer-motion';4import React, { memo, RefObject, useEffect, useRef, useState } from 'react';56const alphabetPaths = {7A: 'M15,90 L50,10 L85,90 M25,60 H75',8B: 'M20,10 V90 H60 Q85,90 85,70 Q85,60 70,50 Q85,40 85,30 Q85,10 60,10 H20',9C: 'M85,25 Q65,10 40,10 Q15,10 15,50 Q15,90 40,90 Q65,90 85,75',10D: 'M20,10 V90 H60 Q90,90 90,50 Q90,10 60,10 H20',11E: 'M80,10 H20 V90 H80 M20,50 H65',12F: 'M20,10 V90 M20,10 H80 M20,50 H65',13G: 'M85,25 Q65,10 40,10 Q15,10 15,50 Q15,90 40,90 Q65,90 85,75 V55 H55',14H: 'M20,10 V90 M80,10 V90 M20,50 H80',15I: 'M30,10 H70 M50,10 V90 M30,90 H70',16J: 'M70,10 H30 M50,10 V75 Q50,90 30,85',17K: 'M20,10 V90 M80,10 L20,50 L80,90',18L: 'M20,10 V90 H80',19M: 'M15,90 V15 L50,50 L85,15 V90',20N: 'M20,90 V15 L80,90 V15',21O: 'M50,10 Q80,10 85,50 Q80,90 50,90 Q20,90 15,50 Q20,10 50,10',22P: 'M20,90 V10 H65 Q85,10 85,35 Q85,60 65,60 H20',23Q: 'M50,10 Q80,10 85,50 Q80,90 50,90 Q20,90 15,50 Q20,10 50,10 M60,70 L85,90',24R: 'M20,90 V10 H65 Q85,10 85,35 Q85,60 65,60 H20 M50,60 L85,90',25S: 'M80,25 Q60,10 35,10 Q15,15 15,35 Q15,50 50,55 Q85,60 85,75 Q85,90 50,90 Q25,90 15,75',26T: 'M15,10 H85 M50,10 V90',27U: 'M15,10 V65 Q15,90 50,90 Q85,90 85,65 V10',28V: 'M15,10 L50,90 L85,10',29W: 'M10,10 L30,90 L50,40 L70,90 L90,10',30X: 'M15,10 L85,90 M15,90 L85,10',31Y: 'M15,10 L50,50 L85,10 M50,50 V90',32Z: 'M15,10 H85 L15,90 H85'33};3435const Letter = memo(36({37letterIndex,38wordPathsRef,39index,40path,41circles,42circlesRef,43circlesPositionsRef,44radius45}: {46letterIndex: number;47index: number;48path: string;49radius: number;50wordPathsRef: RefObject<Record<number, SVGPathElement[]>>;51circles: number;52circlesRef: RefObject<Record<number, SVGCircleElement[][]>>;53circlesPositionsRef: RefObject<{ x: number; y: number }[][]>;54}) => {55return (56<div className="mx-1">57<svg width="50" height="70" viewBox="0 0 100 100" style={{ filter: 'url(#gooey)' }}>58<path59ref={(el) => {60if (el) {61if (!wordPathsRef.current[index]) {62wordPathsRef.current[index] = [];63}64wordPathsRef.current[index][letterIndex] = el;65}66}}67d={path}68fill="none"69strokeLinecap="round"70strokeLinejoin="round"71/>72<g>73{Array.from({ length: circles }).map((_, circleIndex) => (74<circle75ref={(el) => {76if (el) {77if (!circlesRef.current[index]) {78circlesRef.current[index] = [];79}80if (!circlesRef.current[index][letterIndex]) {81circlesRef.current[index][letterIndex] = [];82}83if (!circlesPositionsRef.current[letterIndex]) {84circlesPositionsRef.current[letterIndex] = [];85}86if (!circlesPositionsRef.current[letterIndex][circleIndex]) {87circlesPositionsRef.current[letterIndex][circleIndex] = {88x: 50,89y: 5090};91}9293circlesRef.current[index][letterIndex][circleIndex] = el;94}95}}96r={radius}97key={`circle-${letterIndex}-${circleIndex}-${index}`}98cx={circlesPositionsRef.current?.[letterIndex]?.[circleIndex].x || 50}99cy={circlesPositionsRef.current?.[letterIndex]?.[circleIndex].y || 50}100fill="white"101/>102))}103</g>104</svg>105</div>106);107}108);109110const GooeyWords = ({ words, speed = 2 }: { words: string[]; speed?: number }) => {111const [index, setIndex] = useState(0);112const wordPathsRef = useRef<Record<number, SVGPathElement[]>>({});113const circlesRef = useRef<Record<number, SVGCircleElement[][]>>({});114const circlesPositionsRef = useRef<{ x: number; y: number }[][]>([]);115116const circles = 25;117const radius = 5;118119useEffect(() => {120const interval = setInterval(() => {121setIndex((i) => (i + 1) % words.length);122}, speed * 1000);123124return () => clearInterval(interval);125}, [words.length, speed, index]);126127useEffect(() => {128if (!wordPathsRef.current[index] || wordPathsRef.current[index].length === 0) {129return;130}131132const currentPaths = wordPathsRef.current[index];133const currentCirclesGroups = circlesRef.current[index] || [];134135currentPaths.forEach((path, letterIndex) => {136if (!path) return;137138const length = path.getTotalLength();139if (!length) return;140141const step = length / circles;142const circlesToAnimate = currentCirclesGroups[letterIndex] || [];143144circlesToAnimate.forEach((circle, circleIndex) => {145if (!circle) return;146147const { x, y } = path.getPointAtLength(step * circleIndex) || { x: 0, y: 0 };148149circlesPositionsRef.current[letterIndex][circleIndex] = { x, y };150151animate(152circle,153{ cx: x, cy: y },154{155duration: 0.8,156delay: 0.02 * circleIndex + letterIndex * 0.04,157ease: 'easeOut'158}159);160});161});162}, [index, circles]);163164return (165<div className="relative">166<svg className="absolute w-0 h-0">167<defs>168<filter id="gooey">169<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />170<feColorMatrix171in="blur"172type="matrix"173values="1741 0 0 0 01750 1 0 0 01760 0 1 0 01770 0 0 12 -2"178result="gooey"179/>180</filter>181</defs>182</svg>183184<AnimatePresence mode="popLayout">185<motion.div186className="flex items-center justify-center p-8 rounded-lg"187style={{188minHeight: '100px'189}}190>191{words[index].split('').map((letter, letterIndex) => {192const path = alphabetPaths[letter.toUpperCase() as keyof typeof alphabetPaths];193194if (!path) {195return <div key={`${letter}-${letterIndex}-${index}`} className="w-8" />;196}197198return (199<Letter200key={`${letter}-${letterIndex}-${index}`}201letterIndex={letterIndex}202index={index}203path={path}204radius={radius}205wordPathsRef={wordPathsRef}206circles={circles}207circlesRef={circlesRef}208circlesPositionsRef={circlesPositionsRef}209/>210);211})}212</motion.div>213</AnimatePresence>214</div>215);216};217218export default GooeyWords;
Usage
1<GooeyWords words={words} />
⭐️ Got a question or feedback?
Feel free to reach out!