Build a Blur Text Animation in React
Create a smooth blur-to-sharp text reveal effect using Framer Motion. Each word or character fades from blurred to crisp on scroll.
A plain fade-in is fine. A blur-to-sharp reveal is something else entirely. It gives the impression that the text is materializing out of thin air.
The final result
Play with the controls to see how the component behaves with different settings:
Setting up
This one is lightweight. You only need Framer Motion.
import { useRef } from 'react';
import { motion, useInView } from 'framer-motion';Step 1: Define the props
The key decision is whether you split by words or characters. We keep the API minimal.
interface BlurTextProps {
text: string;
delay?: number;
className?: string;
animateBy?: 'words' | 'characters';
direction?: 'top' | 'bottom';
}Step 2: Split text and detect viewport
Split the text string based on animateBy, then use useInView to trigger when the element scrolls into view.
const segments = animateBy === 'words' ? text.split(' ') : text.split('');
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-50px' });Step 3: Animate each segment
Each segment gets its own motion.span. The blur, opacity, and y-offset animate together for a rich reveal. Try switching between words and characters:
The motion values that make this work:
initial={{ opacity: 0, filter: 'blur(12px)', y: yOffset }}
animate={isInView
? { opacity: 1, filter: 'blur(0px)', y: 0 }
: { opacity: 0, filter: 'blur(12px)', y: yOffset }
}
transition={{ duration: 0.5, delay: i * delay }}Step 4: Fine-tune the timing
The delay between segments controls the feel. Low values (0.02) feel like a wave, high values (0.15) feel deliberate. Play with it:
Key takeaways
- Animating
filter: blur()along withopacityandycreates a much richer reveal than opacity alone. - Framer Motion's
useInViewhook keeps the trigger logic simple and declarative. - The stagger delay is the single most impactful parameter to tune.