use-animated-markdown-content.ts

Jul 8, 2025ยทanimate the rendering of markdown content, usually from an llm
import { useRef } from 'react';
import { animate } from 'framer-motion';
import { marked } from 'marked';

export function useAnimatedMarkdownContent() {
	const containerRef = useRef<HTMLElement>(null);
	const prevContentRef = useRef<string>('');

	const animateContent = (newText: string) => {
		return new Promise<void>((resolve) => {
			const oldText = prevContentRef.current;
			// Fade out old text
			animate(oldText.length, 0, {
				duration: 0.3,
				onUpdate: (latest) => {
					if (containerRef.current) {
						containerRef.current.innerHTML = marked.parse(
							oldText.slice(0, Math.floor(latest))
						) as string;
					}
				},
				onComplete: () => {
					// Fade in new text
					animate(0, newText.length, {
						duration: 0.3,
						onUpdate: (latest) => {
							if (containerRef.current) {
								containerRef.current.innerHTML = marked.parse(
									newText.slice(0, Math.floor(latest))
								) as string;
							}
						},
						onComplete: () => {
							prevContentRef.current = newText;
							resolve();
						},
					});
				},
			});
		});
	};

	const resetContent = () => {
		if (containerRef.current) {
			containerRef.current.innerHTML = '';
		}
		prevContentRef.current = '';
	};

	const renderContent = async (markdown: string) => {
		return marked.parse(markdown) as string;
	};

	const renderContentSync = (markdown: string) => {
		return marked.parse(markdown, { async: false }) as string;
	};

	return {
		containerRef,
		animateContent,
		resetContent,
		renderContent,
		renderContentSync,
	};
}