Impact: HIGH - Fixes common text animation bugs and significantly improves readability
This skill covers patterns for professional text animations, including typewriter effects, cursor blinking, word carousels, and text highlighting.
Typewriter Effect - Use String Slicing
Always use string slicing for typewriter effects. Never use per-character opacity.
Incorrect (per-character opacity - breaks cursor positioning)
Correct (string slicing - cursor follows text)
{
text
. split ( "" )
. map (( char , i ) => (
< span style = { { opacity: i < typedCount ? 1 : 0 } } > { char } </ span >
));
}
< span > | </ span > ;
Per-character opacity creates invisible characters that still occupy space, causing the cursor to appear in the wrong position. Use .slice() instead.
Cursor Blink - Use Smooth Interpolation
Blinking cursors should fade smoothly, not flash on/off abruptly.
Incorrect (abrupt blink)
Correct (smooth blink)
const caretVisible = Math . floor ( frame / 15 ) % 2 === 0 ;
< span style = { { opacity: caretVisible ? 1 : 0 } } > | </ span > ;
Smooth fading creates a more polished, professional appearance compared to harsh on/off toggling.
Word Carousel - Stable Width Container
Prevent layout shifts by using the longest word to set container width.
Incorrect (width jumps between words)
Correct (stable width from longest word)
< div style = { { position: "relative" } } >
< span > { WORDS [ currentIndex ] } </ span >
</ div >
How does the hidden element technique work?
The invisible element reserves space based on the longest word, while the absolutely positioned visible word sits on top. This prevents the container from resizing as words change, eliminating jarring layout shifts.
Text Highlight - Two Layer Crossfade
Use overlapping layers for smooth highlight transitions.
const typedOpacity = interpolate (
frame ,
[ highlightStart - 8 , highlightStart + 8 ],
[ 1 , 0 ],
{ extrapolateLeft: "clamp" , extrapolateRight: "clamp" },
);
const finalOpacity = interpolate (
frame ,
[ highlightStart , highlightStart + 8 ],
[ 0 , 1 ],
{ extrapolateLeft: "clamp" , extrapolateRight: "clamp" },
);
{ /* Typing layer */ }
< div style = { { opacity: typedOpacity } } > { typedText } </ div > ;
{ /* Final layer with highlight */ }
< div style = { { position: "absolute" , inset: 0 , opacity: finalOpacity } } >
< span > { preText } </ span >
< span style = { { backgroundColor: COLOR_HIGHLIGHT } } > { HIGHLIGHT_WORD } </ span >
< span > { postText } </ span >
</ div > ;
The 8-frame overlap creates a smooth crossfade between the typing state and the highlighted state, avoiding abrupt transitions.
Key Patterns
For typewriter effects, always use: const displayed = fullText . slice ( 0 , charCount );
Never use per-character opacity or visibility.
Use interpolation with at least 3 keyframes for smooth fade: interpolate (
frame % CYCLE ,
[ 0 , CYCLE / 2 , CYCLE ],
[ 1 , 0 , 1 ]
)
Reserve space with invisible elements: < div style = { { visibility: "hidden" } } > { longestContent } </ div >
< div style = { { position: "absolute" } } > { visibleContent } </ div >
Overlap opacity transitions for smooth blends: // Layer 1: fade out from frame 52-60
// Layer 2: fade in from frame 56-64
// 4-frame overlap creates smooth crossfade
Common Mistakes to Avoid
Don’t use per-character opacity for typewriter effects - The cursor position will be wrong because invisible characters still take up space.
Don’t use binary (0 or 1) opacity for cursor blink - Use smooth interpolation instead for a professional look.
Don’t let word carousels jump around - Use the longest word to establish stable container dimensions.
Don’t switch highlights abruptly - Use two-layer crossfade technique for smooth transitions.
Typography Best Practices
Choose Readable Fonts
Use web-safe fonts or import from Google Fonts. Ensure sufficient weight and size for video compression.
Plan Timing Carefully
Typewriter speed: ~3-5 characters per second (at 30fps = ~6-10 frames per char) Cursor blink cycle: ~16 frames for natural rhythm
Test Readability
Preview at actual output resolution. Text that looks good in the editor may be too small when rendered.
Spring Physics Add bounce to text entrances
Sequencing Coordinate multiple text elements