Avatar

Hola, I'm Julia.

Creating a React Component That Fades Changing Words In and Out

#javascript #react

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.

fading-words

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

© 2016-2024 Julia Tan · Powered by Next JS.