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>
)
}