useAnimatedText

Smoothly animate frequently–updating text, like text from a chatbot.

Demo

A hook that can animate an updating string character-by-character or word-by-word. It's robust to interruptions, and resets for new string values.

Learn how to build it on YouTube.

Code

// Dependencies: npm i framer-motion

"use client";

import { animate } from "framer-motion";
import { useEffect, useState } from "react";

const delimiter = ""; // or " " to split by word

export function useAnimatedText(text: string) {
  const [cursor, setCursor] = useState(0);
  const [startingCursor, setStartingCursor] = useState(0);
  const [prevText, setPrevText] = useState(text);

  if (prevText !== text) {
    setPrevText(text);
    setStartingCursor(text.startsWith(prevText) ? cursor : 0);
  }

  useEffect(() => {
    const controls = animate(startingCursor, text.split(delimiter).length, {
      // Tweak the animation here
      duration: 4,
      ease: "easeOut",
      onUpdate(latest) {
        setCursor(Math.floor(latest));
      },
    });

    return () => controls.stop();
  }, [startingCursor, text]);

  return text.split(delimiter).slice(0, cursor).join(delimiter);
}

Usage

function Page() {
  const [prompt, setPrompt] = useState('');
  const [text, setText] = useState('');
  const animatedText = useAnimatedText(text); 

  async function handleSubmit() {
    const { textStream } = await streamText({
      model: openai('gpt-4-turbo'),
      prompt,
    });

    for await (const textPart of textStream) {
      setText((prev) => prev + textPart);
    }
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          placeholder="Ask anything"
        />
      </form>

      <p>{animatedText}</p>
    </>
  );
}

Get our latest in your inbox.

Join our newsletter to hear about Sam and Ryan's newest blog posts, code recipes, and videos.