Creating a React Component That Fades Changing Words In and Out
4 min read
Have you ever wanted to inject a bit of whimsy into your app through animations, but shied away from it because...well...animations in React are not always that fun? I hear you! It’s one of the reasons that we don’t tend to use a lot of fancy animations in our web app at work, because we’re not always sure the effort is worth it.
However, sometimes a bit of subtle animation can go along way, especially if they’re easy to implement using regular CSS and React hooks. Here’s an example I mocked up quickly, where I’m transitioning through a number of greeting words with a fade in and out effect. Read on to find out how I implemented this.
Set up the React component
To start, I created a new file for my component called AnimatedText.tsx
. We’ll put in placeholder text for now, just to enable us to quickly render the component on screen. The word between the span
tag is what we’ll be swapping in and out.
export const AnimatedText = () => {
return (
<h2>
<span>Hello</span>, I'm Julia.
</h2>
)
}
Set up the CSS classes
Next, set up the two CSS classes we’ll be toggling between, to create the fade effect. For this, we’ll be using the opacity
attribute (either 0 or 1), and will be transitioning between the two values over a period of 1.5 seconds, with an ease
effect. You don’t need to explicitly define opacity: 1
, as this is the default value.
.fade-in {
transition: opacity 1.5s ease;
}
.fade-out {
opacity: 0;
transition: opacity 1.5s ease;
}
Toggle the CSS classes with useEffect
and setInterval
The general idea is this: we’re going to use JavaScript’s setInterval
method, to call a setState
function. The state will either be one of the 2 CSS classes i.e. either ‘fade-in’
or ‘fade-out’
. If you’re not familiar with the setInterval
method, you can read more about it here, but its job is to run a bit of code repeatedly, with a fixed time delay between each call.
We then want to stick everything into a useEffect
hook to ensure that we continuously run this in a loop whenever our class changes. Don’t forget to include the return function in the useEffect
to ensure you clear out the current interval ID before creating a new one, or you’ll have a memory leak. 🧠 Also, hook up your span
element to the fadeProp
state.
import { useEffect, useState } from 'react'
const FADE_INTERVAL_MS = 1750
type FadeProp = { fade: 'fade-in' | 'fade-out' }
export const AnimatedText = () => {
const [fadeProp, setFadeProp] = useState<FadeProp>({ fade: 'fade-in' })
useEffect(() => {
const fadeTimeout = setInterval(() => {
fadeProp.fade === 'fade-in' ? setFadeProp({ fade: 'fade-out' }) : setFadeProp({ fade: 'fade-in' })
}, FADE_INTERVAL_MS)
return () => clearInterval(fadeTimeout)
}, [fadeProp])
return (
<h2>
<span className={fadeProp.fade}>Hello</span>, I'm Julia.
</h2>
)
}
Change the greeting word on each transition cycle
Using the same concept of a setInterval
and useEffect
, we can also cycle through an array of greeting words on each fade transition cycle. The things to note here:
- Set the time delay for your word change
setInterval
to be double that of the fade interval time delay. This is because you need to account for the time it takes to fade in AND fade out, before changing the word. - The second
useEffect
doesn’t need anything in the dependency array because I referenced the previous state value (this helps to prevent an infinite loop). - Remember to replace the content of your
span
tag to reference the words array andwordOrder
state.
import { useEffect, useState } from 'react'
const FADE_INTERVAL_MS = 1750
const WORD_CHANGE_INTERVAL_MS = FADE_INTERVAL_MS * 2
const WORDS_TO_ANIMATE = ['Hello', 'Ciao', 'Jambo', 'Bonjour', 'Salut', 'Hola', 'Nǐ hǎo', 'Hallo', 'Hej', '👋🏻']
type FadeProp = { fade: 'fade-in' | 'fade-out' }
export const AnimatedText = () => {
const [fadeProp, setFadeProp] = useState<FadeProp>({ fade: 'fade-in' })
const [wordOrder, setWordOrder] = useState(0)
useEffect(() => {
const fadeTimeout = setInterval(() => {
fadeProp.fade === 'fade-in' ? setFadeProp({ fade: 'fade-out' }) : setFadeProp({ fade: 'fade-in' })
}, FADE_INTERVAL_MS)
return () => clearInterval(fadeTimeout)
}, [fadeProp])
useEffect(() => {
const wordTimeout = setInterval(() => {
setWordOrder((prevWordOrder) => (prevWordOrder + 1) % WORDS_TO_ANIMATE.length)
}, WORD_CHANGE_INTERVAL_MS)
return () => clearInterval(wordTimeout)
}, [])
return (
<h2>
<span className={fadeProp.fade}>{WORDS_TO_ANIMATE[wordOrder]}</span>, I'm Julia.
</h2>
)
}