Typewriter
Typewriter effect for text. Supports html and markdown.
Lorem Ipsum is simply dummy text of the printing and typesetting industry
Installation
1
Install the package if you do not have it.
npm install react-markdown2
Copy and paste the following code into your project.
'use client';
import { cn } from '@/lib/utils';
import { useEffect, useRef, useState } from 'react';
import Markdown, { Components } from 'react-markdown';
type TypewriterProps = {
text?: string;
typeSpeed?: number;
className?: string;
onComplete?: () => void;
renderMarkdown?: boolean;
markdownComponents?: Components;
};
export const Typewriter = ({
text = '',
typeSpeed = 33,
onComplete,
className,
renderMarkdown,
markdownComponents,
}: TypewriterProps) => {
const [displayedText, setDisplayedText] = useState('');
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const onCompleteRef = useRef(onComplete); // Ref to store the latest onComplete
// Keep onComplete callback reference up-to-date without causing effect re-runs
useEffect(() => {
onCompleteRef.current = onComplete;
}, [onComplete]);
useEffect(() => {
/***
* Each time htmlString changes,
* only add new characters from the end of the currently displayed text.
*/
const startTyping = () => {
let currentIndex = displayedText.length;
intervalRef.current = setInterval(() => {
if (currentIndex < text.length) {
// Only add new characters, do not reset old text
setDisplayedText(text.slice(0, currentIndex + 1));
currentIndex++;
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
onCompleteRef.current?.();
}
}, typeSpeed);
};
// If there is new text, start typing animation
if (text.length > displayedText.length) {
// Start typing immediately, no need to wait for delay
startTyping();
}
// If targetText decreases or resets, consider handling it自行處理
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text, typeSpeed]);
if (renderMarkdown) {
return (
<div className={className}>
<Markdown components={markdownComponents}>{displayedText}</Markdown>
</div>
);
}
return (
<span
className={cn('whitespace-pre-wrap leading-7', className)}
dangerouslySetInnerHTML={{ __html: displayedText }}
/>
);
};
3
Copy following animation to tailwind.config.js
keyframes: {
'blink-caret': {
'0%, 100%': { opacity: '0' },
'50%': { opacity: '1' },
},
},
'blink-caret': {
'0%, 100%': { opacity: '0' },
'50%': { opacity: '1' },
},
4
Update the import paths to match your project setup.
Markdown
Following markdown style is using tailwindcss-typography
Here is shopping list:
- apple
- banana
- orange
Lorem Ipsum is simply dummy text of
Properties
| Property | Type | Default |
|---|---|---|
text | string | string |
typeSpeed | number | 33 |
renderMarkdown | boolean | |
markdownComponents | Components | |
onComplete | () => void | |
className | string |