PTENES
MODULE 2.1

✍️ Text Animations

9 templates, 9 different ways to move text. From revealing letters one by one with stagger to sine waves and RGB glitch—each template teaches a pattern that recurs in dozens of real-world situations.

9
Templates
40
Minutes
Inter
Level
Practical
Type
string "REMOTION" R E M O T I +0 +3 +6 +9 +12 +15 delay = i × 3 frames (stagger) REMOTION character-by-character reveal video animated
1

✍️ Overview — The Text category

Animated text isn’t optional in video—it’s the primary vehicle for information. The Text category in the library covers 9 distinct patterns, from revealing letters one at a time to a sine wave and RGB glitch. Knowing which one to use in each context is what separates a shallow video from one with a visual identity.

🎯 When to use text animation

Reveal information progressively:

  • →animated-text or typewriter for data that “arrives”
  • →slide-text for directional entrances (slide-in)

Draw attention to existing text:

  • →pulsing-text for gentle, continuous breathing
  • →glitch-text for a retro/hacker style

✓ When text animates well

  • ✓Pacing aligned with narration or background music.
  • ✓Duration proportional to the length of the text.
  • ✓Animation style consistent with the video’s tone.

✗ When it hurts

  • ✗Animation faster than reading—the text disappears before it can be read.
  • ✗Glitch or bounce in a serious corporate context.
  • ✗Multiple different animations in the same scene.
9 templates
Text Category
3 families
Stagger / Physics / Effect
Base hook
interpolate + spring
Pattern
Split → stagger → animate
2

🔤 animated-text — letter-by-letter stagger

The template animated-text.tsx splits the text into an array of characters and applies a Sequence with from scaled by index. Each letter comes in with a slight delay after the previous one, creating the effect of “fast typing” without using setTimeout or state.

animated-text.tsx — stagger core templates/animated-text.tsx
const frame = useCurrentFrame();
const chars = text.split("");

return (
  <div style={{ display: "flex" }}>
    {chars.map((char, i) => {
      const opacity = interpolate(
        frame,
        [i * 2, i * 2 + 8],
        [0, 1],
        { extrapolateRight: "clamp" }
      );
      return <span key={i} style={{ opacity }}>{char}</span>;
    })}
  </div>
);

💡 Why i * 2 instead of a fixed value?

O i * 2 is the delay: each letter starts 2 frames after the previous one. Change it to i * 4 and the reveal is slower; i * 1 makes it almost simultaneous. The + 8 sets the duration of each individual fade — the time it takes for opacity to go from 0 to 1.

Execution timeline (text "OLÁ"):

F0

Frame 0 — "O" starts to appear

The “O” opacity goes from 0→1 over frames 0-8. “L” and “Á” are still invisible (opacity=0).

F2

Frame 2 — "L" begins its fade

"O" still animating; "L" starts interpolation from 0→1 on frames 2-10.

F8

Frame 8 — full text visible

All characters at opacity=1. Animation complete.

Pattern
split + map + interpolate
Stagger
i × delay
Extrapolate
clamp (required)
Reuse in
Words, lines, lists
3

🏀 bounce-text — spring with overshoot

O bounce-text.tsx uses spring() instead of interpolate(). The visual difference is huge: instead of moving up linearly, the text “bounces”—overshoots its destination and comes back, like an object with mass and damping.

bounce-text — spring with parameters templates/bounce-text.tsx
const { fps } = useVideoConfig();
const frame = useCurrentFrame();

const scale = spring({
  fps,
  frame,
  config: {
    damping: 8,     // baixo = mais quique
    stiffness: 120, // alto = mais rápido
    mass: 1,
  },
});

return <div style={{ transform: `scale(${scale})` }}>{text}</div>;

✓ Spring: when to use it

  • ✓Hero titles that need visual impact.
  • ✓CTAs and buttons that "call out" to the viewer.
  • ✓Emojis and expressive icons.

✗ Interpolate: when to use it

  • →Fades—spring doesn’t control opacity precisely.
  • →Exact horizontal movement (precise slide).
  • →Any timing that can't overshoot.

📊 Spring parameter table

ParameterLow valueHigh value
dampingelastic, oscillates too muchsnaps quickly, no bounce
stiffnessslow, lazyfast, aggressive
masslight, fastheavy, high inertia
Returns
0 → ~1 (with overshoot)
Uses fps
always pass fps
Combine
× scale or translateY
Stagger
frame - i * delay
4

💻 glitch-text — RGB split without a library

The glitch effect is achieved with three overlapping copies of the same text, each colored in one channel (red, green, blue) and shifted horizontally by a different amount. All with random() seeded and interpolate multi-keyframe for the pulses.

glitch-text — RGB split templates/glitch-text.tsx
// deslocamento pulsante para canal vermelho
const glitchX = interpolate(
  frame % 12,                    // pulso periódico
  [0, 2, 4, 6, 12],
  [0, -4, 3, -2, 0],
  { extrapolateRight: "clamp" }
);

return (
  <div style={{ position: "relative" }}>
    {/* cópia vermelha */}
    <span style={{ color:"red", position:"absolute",
      transform:`translateX(${glitchX}px)`,
      mixBlendMode: "screen" }}>{text}</span>
    {/* cópia ciano e texto original */}
  </div>
);

💡 mix-blend-mode: screen

With mixBlendMode: "screen", where red and cyan overlap to produce white—creating the characteristic visual artifact of CRT glitch. Without the blend mode, the colors simply cover each other.

⚠️ Don’t use Math.random() here

The glitch displacement needs to be deterministic—use random("glitch-r") from Remotion for fixed per-channel amplitudes. Math.random() will make the glitch flash differently on each render, breaking the video in parallel rendering.

Technique
3 overlapping copies
Blend
mixBlendMode: screen
Pulse
frame % period
Seed
deterministic random()
5

🌊 floating-bubble-text — sine wave

Instead of interpolate or spring, the floating-bubble-text.tsx uses directly Math.sin() with the frame as an argument. Each character oscillates with a phase difference relative to the previous one, creating a wave that travels continuously across the text — with no beginning or end.

sine wave per character templates/floating-bubble-text.tsx
const frame = useCurrentFrame();

{text.split("").map((char, i) => {
  const y = Math.sin(
    frame / 12       // velocidade da onda
    + i * 0.5     // fase por índice
  ) * 8;           // amplitude em px

  return (
    <span key={i} style={{ display:"inline-block",
      transform: `translateY(${y}px)` }}>
      {char}
    </span>
  );
})}

📊 Adjusting the behavior

frame / 12 — speed: higher number = slower
i * 0.5 — phase offset: lower = more synchronized
* 8 — amplitude: vertical displacement in pixels
Technique
Math.sin(frame)
Loop
Automatic periodic
Phase
i × offset
Usage
Backgrounds and decoration
6

⌨️ typewriter-subtitle — blinking cursor

O typewriter-subtitle.tsx emulates real typing: progressively reveals more characters of the text using text.slice(0, visibleChars), where visibleChars comes directly from the frame. The cursor blinks with multi-keyframe interpolation.

typewriter + blinking cursor templates/typewriter-subtitle.tsx
const frame = useCurrentFrame();
const charsPerFrame = 0.3; // velocidade de digitação

const visibleChars = Math.floor(frame * charsPerFrame);
const displayText = text.slice(0, visibleChars);

// cursor pisca a cada ~15 frames
const cursorOpacity = interpolate(
  frame % 20,
  [0, 10, 20],
  [1, 0, 1]
);

💡 Adjusting the speed

charsPerFrame = 0.3 types ~9 chars/sec at 30fps. To type faster: increase to 0.5. For slow, terminal-style typing: 0.15. The formula Math.floor(frame * rate) is the universal pattern for “how many elements to show so far.”

Technique
slice(0, frame×rate)
Cursor
frame % period
Speed
charsPerFrame
Usage
Captions, code, tutorials
7

📚 Complete Library — Text Category

All 9 templates in the category, with the file, a one-line description, and the main hook used.

TemplateDescriptionMain hook
animated-text.tsx Character-by-character reveal with stagger interpolate
bounce-text.tsx Entrance with elastic overshoot spring
bubble-pop-text.tsx Scale 0→1.2→1 per word spring
floating-bubble-text.tsx Sine wave per character Math.sin
glitch-text.tsx RGB split with irregular pulse interpolate + random
popping-text.tsx Independent pop scale per word spring stagger
pulsing-text.tsx Continuous opacity breathing Math.sin loop
slide-text.tsx Directional slide with easing out interpolate + easing
typewriter-subtitle.tsx Typing with a blinking cursor slice + frame%

📌 Module summary

✓
Stagger = split + map + i × delay — the universal pattern for revealing elements in sequence.
✓
spring for an organic pop — low damping = bounce, high damping = firm.
✓
Math.sin(frame) for perpetual loops — wave, breathing, and any cyclical movement.
✓
Glitch = 3 copies + mixBlendMode — complex visual effect without an external library.
✓
seeded random() — visual randomness without breaking deterministic rendering.

Next module:

2.2 — Charts & Data: animated SVG with interpolate, dashoffset, arcs, and numeric counters.