Animated Paragraph

A text animation using text opacity on scroll. Made with Framer Motion & React

This is a fun and easy animation to add some interactivity to your site. We'll start by animating a paragraph as a whole to finish with a word-by-word animation - which in my opinion is nicer and doesn't overcomplicate things.

Paragraph animation

Both these methods are going to be relying on the useScroll hook from Framer Motion to animate the opacity on scroll.

import { motion, useScroll } from 'framer-motion'
import React from 'react'
type AnimatedParagraphProps = {
value: string
}
export function AnimatedParagraph({ value }: AnimatedParagraphProps) {
const ref = React.useRef<HTMLParagraphElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start 0.85', 'start 0.3'],
})
return (
<motion.p ref={ref} style={{ opacity: scrollYProgress }} className={'flex w-fit flex-wrap'}>
{value}
</motion.p>
)
}
Animated Paragraph Gif

Word-by-word animation

This is quite similar to the previous method except we are splitting the string into words and then animating each of them.

import React from 'react'
import { MotionValue, useScroll, useTransform, motion } from 'framer-motion'
type AnimatedWordProps = {
value: string
}
export function AnimatedWord({ value }: AnimatedWordProps) {
const ref = React.useRef<HTMLParagraphElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start 0.85', 'start 0.3'],
})
const words = value.toString().split(' ')
return (
<p ref={ref} className={'mx-auto flex w-fit max-w-5xl flex-wrap text-7xl font-semibold text-white'}>
{words.map((word, i) => {
const start = i / words.length
const end = start + 1 / words.length
return (
<Word key={i} range={[start, end]} scrollYProgress={scrollYProgress}>
{word}
</Word>
)
})}
</p>
)
}
type WordProps = {
children: string
range: [number, number]
scrollYProgress: MotionValue<number>
}
function Word({ children, range, scrollYProgress }: WordProps) {
const opacity = useTransform(scrollYProgress, range, [0, 1])
return (
<span className={'relative mr-2 text-pretty leading-none'}>
<span className={'absolute opacity-20'}>{children}</span>
<motion.span style={{ opacity }}>{children}</motion.span>
</span>
)
}
Animated Word by word Gif