Power Pilot
Published on

Authors
  • avatar
    Name
    Seb Burrell
    Twitter

Making Static Content Come Alive

When I started building my blog, I wanted it to stand out. Most developer blogs are static and lifeless - just words on a screen. I envisioned something more dynamic, something that would engage readers from the moment they landed on my site. That's when I decided to create TokenizedText - a React component that brings text to life through fluid, typewriter-style animations.

The Vision

I had three main goals for this component:

  1. Create smooth, natural-feeling text animations
  2. Ensure it was fully responsive and accessible
  3. Make it easily reusable across my blog

The result is a versatile component that animates text rendering word by word, with a dynamic cursor that adapts to any font size or style. Let's explore what it can do.

Basic Usage

The simplest use case is animating a single line of text:

Show Code
<TokenizedText
  key={demoKey}
  text="This is the default animation speed with default settings."
  typingSpeed={50}
/>

Delayed Animation

You can add dramatic effect by controlling the timing:

Show Code
<TokenizedText
  key={demoKey}
  text="This text reveals much slower, creating a more dramatic effect..."
  typingSpeed={150}
  initialCursorDelay={2000}
/>

Sequential Animations

Chain multiple animations together using the onComplete callback:

Show Code
const [step, setStep] = useState(1)
const nextStep = useCallback(() => setStep(prev => prev + 1), [])

{step >= 1 && (

<TokenizedText text="First, this text appears..." typingSpeed={50} onComplete={nextStep} />
)}
{step >= 2 && <TokenizedText text="Then this follows..." typingSpeed={50} onComplete={nextStep} />}
{step >= 3 && <TokenizedText text="And finally, this appears!" typingSpeed={50} />}

Font Size Adaptation

The component automatically adapts to different font sizes:

Show Code
<div>
  <div className="mb-4 text-2xl">
    <TokenizedText
      text="This text is larger!"
      typingSpeed={50}
    />
  </div>
  <div className="text-sm">
    <TokenizedText
      text="And this text is smaller..."
      typingSpeed={50}
    />
  </div>
</div>

Styling Options

Combine with Tailwind classes for endless styling possibilities:

Show Code
<div key={demoKey} className="space-y-4">
  <div className="font-bold text-primary-500">
    <TokenizedText text="This text is bold and uses your primary theme color!" typingSpeed={50} />
  </div>
  <div className="text-xl italic">
    <TokenizedText text="Italicized larger text..." typingSpeed={50} />
  </div>
  <div className="rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
    <TokenizedText
      text="Text in a styled container with padding and rounded corners."
      typingSpeed={50}
    />
  </div>
</div>

Technical Details

Let's look at some of the key technical features that make this component special:

Dynamic Cursor Sizing

One of the trickiest parts was creating a cursor that perfectly matches text height regardless of font size or styling:

const [cursorDimensions, setCursorDimensions] = useState({
  width: 0,
  height: 0,
})

useEffect(() => {
  const updateCursorSize = () => {
    if (measureRef.current) {
      const computedStyle = window.getComputedStyle(measureRef.current)
      const lineHeight = parseFloat(computedStyle.lineHeight)
      const fontSize = parseFloat(computedStyle.fontSize)

      setCursorDimensions({
        width: Math.max(fontSize * 0.4, 2),
        height: !isNaN(lineHeight) ? lineHeight : fontSize * 1.2,
      })
    }
  }

  updateCursorSize()

  const resizeObserver = new ResizeObserver(updateCursorSize)
  if (measureRef.current) {
    resizeObserver.observe(measureRef.current)
  }

  return () => resizeObserver.disconnect()
}, [])

Fluid Height Transitions

Managing height transitions smoothly required careful state management and CSS:

const [height, setHeight] = useState<number>(0)

useEffect(() => {
  if (visibleTextRef.current) {
    const newHeight = visibleTextRef.current.offsetHeight
    setHeight(newHeight)
  }
}, [text, tokens])

// In the render:
<div
  className="overflow-hidden transition-height duration-300 ease-in-out"
  style={{
    height: `${height}px`,
    whiteSpace: 'pre-wrap',
  }}
>

Props API

The component exposes a simple but powerful API:

interface TokenizedTextProps {
  text: string // The text to animate
  typingSpeed?: number // Speed of text appearance (ms)
  delay?: number // Initial delay before animation starts
  initialCursorDelay?: number // Delay before text starts after cursor appears
  className?: string // Additional CSS classes
  onComplete?: () => void // Callback when animation completes
}

Use Cases

I've found numerous ways to use this component throughout my blog:

  1. Hero Sections: Create engaging page introductions
  2. Section Headers: Add emphasis to important content divisions
  3. Code Demonstrations: Animate code examples as they're explained
  4. Interactive Tutorials: Guide users through step-by-step processes

Future Improvements

I'm actively working on several enhancements:

  1. Add support for custom easing functions
  2. Implement pause/resume functionality
  3. Add support for HTML content within the text
  4. Create preset animation patterns
  5. Add RTL text support

Getting Started

The component is now available on npm:

npm install react-tokenized-text

Basic usage is straightforward:

import { TokenizedText } from 'react-tokenized-text'

function Welcome() {
  return (
    <TokenizedText
      text="Hello, welcome to my blog!"
      typingSpeed={50}
      onComplete={() => console.log('Animation complete!')}
    />
  )
}

Conclusion

The source code is available on GitHub, and I welcome contributions and feature suggestions. Feel free to reach out if you have any questions or ideas for improvement!