# Advanced Text Effects Premium text animations beyond basic reveals: glitch, kinetic typography, morphing, distortion, and more. ## Table of Contents - [Glitch Text](#glitch-text) - [Kinetic Typography](#kinetic-typography) - [Morphing Text](#morphing-text) - [Typewriter with Distortion](#typewriter-with-distortion) - [Text Explosion](#text-explosion) - [Circular & Spiral Text](#circular--spiral-text) - [Brutalist Text](#brutalist-text) - [Minimal Text](#minimal-text) - [Advanced Scramble](#advanced-scramble) - [Stroke Text Drawing](#stroke-text-drawing) --- ## Glitch Text RGB split, scan lines, and random character swapping. ```tsx 'use client' import { useRef, useEffect, useState } from 'react' import { gsap } from '@/lib/gsap' export function GlitchText({ text, intensity = 'medium' }: { text: string; intensity?: 'low' | 'medium' | 'high' }) { const containerRef = useRef(null) useEffect(() => { const el = containerRef.current! const layers = el.querySelectorAll('.glitch-layer') const offsets = { low: 2, medium: 5, high: 12 } const offset = offsets[intensity] const tl = gsap.timeline({ repeat: -1, repeatDelay: 2 }) // Glitch burst tl.to(layers[0], { x: -offset, duration: 0.05, ease: 'none' }, 0) .to(layers[0], { x: offset, duration: 0.05, ease: 'none' }, 0.05) .to(layers[0], { x: 0, duration: 0.05, ease: 'none' }, 0.1) .to(layers[1], { x: offset, duration: 0.05, ease: 'none' }, 0.02) .to(layers[1], { x: -offset, duration: 0.05, ease: 'none' }, 0.07) .to(layers[1], { x: 0, duration: 0.05, ease: 'none' }, 0.12) // Clip-path glitch .to(el, { clipPath: `inset(${Math.random() * 40}% 0 ${Math.random() * 40}% 0)`, duration: 0.05, }, 0.15) .to(el, { clipPath: 'inset(0 0 0 0)', duration: 0.05 }, 0.2) return () => { tl.kill() } }, [intensity]) return (
{text} {text} {text} {/* Scan lines overlay */}
) } ``` ### CSS-Only Glitch (Lightweight Alternative) ```css .glitch { position: relative; font-family: monospace; font-weight: 900; } .glitch::before, .glitch::after { content: attr(data-text); position: absolute; inset: 0; } .glitch::before { color: cyan; animation: glitch-shift 3s infinite; clip-path: inset(20% 0 30% 0); mix-blend-mode: multiply; } .glitch::after { color: red; animation: glitch-shift 3s infinite reverse; clip-path: inset(50% 0 10% 0); mix-blend-mode: multiply; } @keyframes glitch-shift { 0%, 90%, 100% { transform: translateX(0); } 92% { transform: translateX(-5px); } 94% { transform: translateX(5px); } 96% { transform: translateX(-3px); } 98% { transform: translateX(3px); } } ``` ## Kinetic Typography Letters that orbit, wave, and bounce independently. ```tsx 'use client' import { useRef } from 'react' import { gsap, useGSAP } from '@/lib/gsap' export function WaveText({ text, amplitude = 20, frequency = 0.15, speed = 2 }: { text: string; amplitude?: number; frequency?: number; speed?: number }) { const containerRef = useRef(null) useGSAP(() => { const chars = containerRef.current!.querySelectorAll('.wave-char') chars.forEach((char, i) => { gsap.to(char, { y: `+=${amplitude}`, duration: speed, repeat: -1, yoyo: true, ease: 'sine.inOut', delay: i * frequency, }) }) }, { scope: containerRef }) return (
{text.split('').map((char, i) => ( {char === ' ' ? '\u00A0' : char} ))}
) } ``` ### Orbiting Letters ```tsx 'use client' import { useRef, useEffect } from 'react' export function OrbitText({ text, radius = 120, speed = 0.01 }: { text: string; radius?: number; speed?: number }) { const canvasRef = useRef(null) const animRef = useRef(0) useEffect(() => { const canvas = canvasRef.current! const ctx = canvas.getContext('2d')! const w = canvas.offsetWidth const h = canvas.offsetHeight canvas.width = w * 2 canvas.height = h * 2 ctx.scale(2, 2) let time = 0 const chars = text.split('') const animate = () => { ctx.clearRect(0, 0, w, h) ctx.font = 'bold 24px monospace' ctx.textAlign = 'center' ctx.textBaseline = 'middle' chars.forEach((char, i) => { const angle = (i / chars.length) * Math.PI * 2 + time const x = w / 2 + Math.cos(angle) * radius const y = h / 2 + Math.sin(angle) * radius * 0.4 // Elliptical const scale = 0.6 + (Math.sin(angle) + 1) * 0.2 // Depth ctx.save() ctx.globalAlpha = 0.4 + (Math.sin(angle) + 1) * 0.3 ctx.font = `bold ${24 * scale}px monospace` ctx.fillStyle = `hsl(${(i / chars.length) * 360}, 70%, 65%)` ctx.fillText(char, x, y) ctx.restore() }) time += speed animRef.current = requestAnimationFrame(animate) } animate() return () => cancelAnimationFrame(animRef.current) }, [text, radius, speed]) return } ``` ## Morphing Text Smooth transition between words. ```tsx 'use client' import { useRef, useEffect, useState } from 'react' import { gsap } from '@/lib/gsap' export function MorphText({ words, interval = 3000 }: { words: string[]; interval?: number }) { const text1Ref = useRef(null) const text2Ref = useRef(null) const [index, setIndex] = useState(0) useEffect(() => { const el1 = text1Ref.current! const el2 = text2Ref.current! el1.textContent = words[index] el2.textContent = words[(index + 1) % words.length] const tl = gsap.timeline({ onComplete: () => setIndex((index + 1) % words.length), delay: interval / 1000, }) tl.to(el1, { opacity: 0, filter: 'blur(8px)', duration: 0.8, ease: 'power2.in', }) .fromTo(el2, { opacity: 0, filter: 'blur(8px)', }, { opacity: 1, filter: 'blur(0px)', duration: 0.8, ease: 'power2.out', }, '-=0.4') return () => { tl.kill() } }, [index, words, interval]) return (
{/* Invisible spacer */} {words.reduce((a, b) => a.length > b.length ? a : b)}
) } ``` ## Typewriter with Distortion Classic typewriter with noise displacement. ```tsx 'use client' import { useState, useEffect, useRef } from 'react' import { gsap } from '@/lib/gsap' export function TypewriterDistortion({ text, speed = 50 }: { text: string; speed?: number }) { const [displayed, setDisplayed] = useState('') const containerRef = useRef(null) const charIndex = useRef(0) useEffect(() => { const timer = setInterval(() => { if (charIndex.current < text.length) { setDisplayed(text.slice(0, charIndex.current + 1)) charIndex.current++ // Distortion shake on each character if (containerRef.current) { gsap.fromTo(containerRef.current, { x: (Math.random() - 0.5) * 4, y: (Math.random() - 0.5) * 2, skewX: (Math.random() - 0.5) * 2, }, { x: 0, y: 0, skewX: 0, duration: 0.15, ease: 'power2.out', }) } } else { clearInterval(timer) } }, speed) return () => clearInterval(timer) }, [text, speed]) return (
{displayed} |
) } ``` ## Text Explosion Characters scatter with physics-like motion. ```tsx 'use client' import { useRef } from 'react' import { motion } from 'motion/react' export function TextExplosion({ text, trigger = false }: { text: string; trigger?: boolean }) { const chars = text.split('') return (
{chars.map((char, i) => { const angle = ((i / chars.length) * 360 + Math.random() * 60) * (Math.PI / 180) const distance = 200 + Math.random() * 300 const targetX = Math.cos(angle) * distance const targetY = Math.sin(angle) * distance return ( {char === ' ' ? '\u00A0' : char} ) })}
) } ``` ## Circular & Spiral Text SVG textPath for text on a circular path. ```tsx 'use client' import { motion } from 'motion/react' export function CircularText({ text, radius = 100, speed = 20 }: { text: string; radius?: number; speed?: number }) { const id = `circle-path-${Math.random().toString(36).slice(2)}` return ( {text} ) } ``` ### Spiral Text (Canvas) ```tsx 'use client' import { useRef, useEffect } from 'react' export function SpiralText({ text, turns = 3 }: { text: string; turns?: number }) { const canvasRef = useRef(null) useEffect(() => { const canvas = canvasRef.current! const ctx = canvas.getContext('2d')! const w = canvas.offsetWidth const h = canvas.offsetHeight canvas.width = w * 2 canvas.height = h * 2 ctx.scale(2, 2) const cx = w / 2, cy = h / 2 const chars = text.split('') const maxRadius = Math.min(w, h) * 0.4 ctx.textAlign = 'center' ctx.textBaseline = 'middle' chars.forEach((char, i) => { const t = i / chars.length const angle = t * Math.PI * 2 * turns const radius = 20 + t * (maxRadius - 20) const x = cx + Math.cos(angle) * radius const y = cy + Math.sin(angle) * radius ctx.save() ctx.translate(x, y) ctx.rotate(angle + Math.PI / 2) ctx.font = `${14 + t * 12}px monospace` ctx.fillStyle = `hsl(${t * 360}, 70%, 60%)` ctx.fillText(char, 0, 0) ctx.restore() }) }, [text, turns]) return } ``` ## Brutalist Text Oversized mono, extreme overlap, aggressive rotation, mixed scales. ```tsx 'use client' import { motion } from 'motion/react' export function BrutalistHeading({ text }: { text: string }) { const words = text.split(' ') return (
{words.map((word, i) => ( 0 ? '-2vw' : '0', mixBlendMode: i % 2 === 0 ? 'normal' : 'difference', }} initial={{ x: i % 2 === 0 ? -200 : 200, rotate: (Math.random() - 0.5) * 15 }} whileInView={{ x: 0, rotate: (Math.random() - 0.5) * 3 }} viewport={{ once: true }} transition={{ duration: 0.3, ease: 'linear' }} > {word} ))}
) } ``` ## Minimal Text Ultra-subtle fades, weight transitions, tracking animations. ```tsx 'use client' import { motion } from 'motion/react' export function MinimalText({ text }: { text: string }) { return ( {text} ) } ``` ### Weight Transition Text ```tsx 'use client' import { useRef } from 'react' import { gsap, useGSAP } from '@/lib/gsap' export function WeightReveal({ text }: { text: string }) { const containerRef = useRef(null) useGSAP(() => { const chars = containerRef.current!.querySelectorAll('span') gsap.fromTo(chars, { fontWeight: 100, opacity: 0.3, }, { fontWeight: 700, opacity: 1, duration: 1, stagger: 0.05, ease: 'power2.out', scrollTrigger: { trigger: containerRef.current, start: 'top 80%', }, }) }, { scope: containerRef }) return (
{text.split('').map((char, i) => ( {char === ' ' ? '\u00A0' : char} ))}
) } ``` ## Advanced Scramble Matrix-style decode and glyph cycling. ```tsx 'use client' import { useState, useEffect, useRef, useCallback } from 'react' const GLYPHS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*' const BINARY = '01' export function MatrixDecode({ text, mode = 'matrix', revealSpeed = 30 }: { text: string; mode?: 'matrix' | 'binary' | 'glyphs'; revealSpeed?: number }) { const [display, setDisplay] = useState('') const revealed = useRef(0) const chars = mode === 'binary' ? BINARY : GLYPHS const scramble = useCallback(() => { let result = '' for (let i = 0; i < text.length; i++) { if (i < revealed.current) { result += text[i] } else if (text[i] === ' ') { result += ' ' } else { result += chars[Math.floor(Math.random() * chars.length)] } } return result }, [text, chars]) useEffect(() => { revealed.current = 0 const scrambleInterval = setInterval(() => setDisplay(scramble()), 50) const revealInterval = setInterval(() => { revealed.current++ if (revealed.current > text.length) { clearInterval(revealInterval) clearInterval(scrambleInterval) setDisplay(text) } }, revealSpeed) return () => { clearInterval(scrambleInterval) clearInterval(revealInterval) } }, [text, revealSpeed, scramble]) return ( {display} ) } ``` ## Stroke Text Drawing SVG text with animated stroke. ```tsx 'use client' import { motion } from 'motion/react' export function StrokeText({ text, fontSize = 80, duration = 2 }: { text: string; fontSize?: number; duration?: number }) { return ( {text} {/* Fill reveal after stroke */} {text} ) } ``` ## Choosing the Right Effect | Context | Effect | Why | |---------|--------|-----| | Hero heading | Stroke Drawing or Morph | Dramatic first impression | | Error/404 page | Glitch | Fits the broken theme | | Loading/processing | Scramble/Decode | Communicates computation | | Creative portfolio | Kinetic/Orbit | Shows motion design skill | | Brand statement | Brutalist or Minimal | Strong identity signal | | Interactive element | Explosion | Satisfying user feedback | | Narrative section | Typewriter | Builds anticipation | | Navigation | Circular | Unique, memorable |