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