Text Reveal Effect

webtailwindreactstylingcssanimation

In this post I implement a text reveal effect in React. Let's begin with outlining the requirements and game plan:

  • Text will be wrapped in a block component with each individual character in it's own in-line element.
  • For each character we'll apply an animation effect to it along with a delay, providing the staggering effect.
  • We want the text to initially nt be visible, then translate in from below.

As for the animation and styling, I'll be using TailwindCSS within the NextJS application. With a NextJS application you can define custom animations within the configuration file tailwind.config.ts as follows.

  extend: {
    animation: {
      "text-reveal": "text-reveal 1.5s linear 1.0s",
    },
    keyframes: {
      "text-reveal": {
        "0%": {
          transform: "translate(0, 100%)",
        },
        "100%": {
          transform: "translate(0, 0)",
        },
      },
    },
  }

We define a custom animation text-reveal. Within the keyframes object we define 0 and 100 percent transforms such that the end of the animation places the element in its intended position. When defining the animation, we apply animation duration, timing function, and delay respectively.

Now let's define the component. We extract each character of the input string into a span tag with the inline-block classname, resulting in each string appearing on the same line. Next we apply our text-reveal animation we defined above using Tailwind's animate-* utility. We also use the [animation-fill-mode:backwards] utility to specify that the elements should remain in the position where their animation ends. To complete the animation effect, we add an animationDelay to each character. The delay amount is dependent on its index position within the string.

  const text = "Welcome to the site 👋";
    return (
      <h2>
        {text.split('').map((char, index) => (
          <span
            className="animate-text-reveal inline-block [animation-fill-mode:backwards]"
            key={`${char}-${index}`}
            style={{ animationDelay: `${index * 0.05}s` }}
          >
            {char}
          </span>
        ))}
      </h2>
    )

Welcome to the site ��

Hmm, not exactly what we want. Firstly I'd prefer a quicker start to the animation that tappers off slowly at the end. Instead of the linear timing function when can use a cubic-bezier function like so cubic-bezier(0.8, 0, 0.2, 1) to get this effect. Also with the animation the text is visible at the start. We'd like this to be hidden which can be achieved by specifying overflow as hidden on the parent element.

Next there are a few issues with rendering the text. We see there is no whitespace between the words. This is due to how React render's whitespace characters: we'll need to add a check to ensure a whitespace character is rendered as expected.

It also looks like something went wrong rendering the emoji character. After some digging, I found that this because the split function only works with inputs that fall into the basic multilingual plane which emoji's do not. A workaround to this is to use the spread operator to tokenize the string instead:

const text = "Welcome to the site 👋";
return (
  <h2 className="overflow-hidden">
    {[...text].map((char, index) => (
      <span
        className="animate-text-reveal inline-block [animation-fill-mode:backwards]"
        key={`${char}-${index}`}
        style={{ animationDelay: `${index * 0.05}s` }}
      >
        {char !== " " ? char : "\u00A0"}
      </span>
    ))}
  </h2>
)

Welcome to the site 👋

Thanks for reading! Note that this was just an exercise I worked through to practice animations with Tailwind. For more complex animations, you may want to checkout Frame Motion, which is a library I've used with other components of this site, if you're looking for more deployment-ready animations.