Source Code

<div class="scene">
  <h1 class="gravity-heading" id="heading">Letters fall into place</h1>
  <button class="replay" onclick="dropLetters()">Drop again</button>
</div>

Letter Gravity Drop Text Animation — Bounce Physics JS

Letter Gravity Drop Text · Animations · Plain HTML, CSS & JS · Live preview

What's included

Features

Real velocity/acceleration simulation per letter, not a sampled easing curve
requestAnimationFrame loop with delta-time integration for frame-rate independence
Bounce damping (velocity reversed and reduced) produces diminishing bounce heights
Per-letter independent physics state — staggered starts never desync existing bounces
GRAVITY, BOUNCE_DAMPING and stagger interval are single tunable constants
Splits any headline into per-letter spans automatically, with space handling
Replay button resets every letter above the baseline and reruns the cascade
Zero dependencies — no physics or animation library required
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

Letter Gravity Drop Text — Gravity Acceleration, Bounce Damping & requestAnimationFrame

Screenshot of the Letter Gravity Drop Text snippet rendered live

A letter gravity drop animates each character of a headline falling from above the baseline under simulated gravity, bouncing once or twice off the resting position with diminishing height, and settling — the way a dropped ball behaves, applied to typography. It reads as more physically grounded than a CSS ease-out, because the motion is computed frame by frame from actual velocity and acceleration rather than sampled from a fixed easing curve. For a rotational, hinge-style entrance instead of a vertical drop, see 3D character flip reveal; for a scale-based spring instead of gravity, see word spring bounce heading.

Splitting into letters and priming them off-screen

buildLetters(el) wraps every character of the heading in its own <span class="letter">, converting spaces to non-breaking spaces so word gaps hold their width. Before dropping, dropLetters() sets every letter's opacity to 0 and transform to translateY(-260px) — parking each letter well above its resting line, invisible, ready to fall.

Simulating gravity with velocity and acceleration

Rather than a CSS @keyframes curve, each letter's fall is computed manually inside a requestAnimationFrame loop. velocity += GRAVITY * dt accelerates the letter downward every frame (GRAVITY = 2600, an arbitrary pixels/second² constant tuned for a snappy but visible fall), and y += velocity * dt integrates that velocity into a position update. dt is the real elapsed time since the previous frame ((now - last) / 1000, clamped to avoid a huge jump after a tab is backgrounded), so the fall speed is consistent regardless of the display's refresh rate.

The bounce

When y >= restY (the letter has reached or passed its resting baseline), the code clamps y back to restY and reverses the velocity with damping: velocity = -velocity * BOUNCE_DAMPING (BOUNCE_DAMPING = 0.42). Because the reflected velocity is smaller than the impact velocity, each successive bounce is lower than the last — exactly like a real dropped object losing energy to an inelastic surface. The loop keeps bouncing until the velocity magnitude falls below a small threshold or a bounce-count cap is hit, at which point it snaps to exactly translateY(0px) and resolves.

Staggering the drops

dropLetters() calls animateDrop(l, i * 45) for every letter, delaying the start of each letter's fall by 45ms times its index via setTimeout. Because each letter runs its own independent requestAnimationFrame loop with its own velocity state, letters that start falling later do not interfere with ones already bouncing — the whole headline settles in a cascading, slightly chaotic rhythm rather than a mechanically uniform one.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how the velocity and position variables update every frame inside the requestAnimationFrame loop, and why BOUNCE_DAMPING being less than 1 is what makes each successive bounce lower than the last — try changing GRAVITY and BOUNCE_DAMPING together and predicting how the motion will feel before testing it. It's also worth a performance conversation: since every letter runs its own independent requestAnimationFrame loop simultaneously, ask whether a single shared loop driving an array of letter states would be more efficient for very long headlines, and at what letter count that would start to matter. For extending it, ask for a version where letters bounce off each other (basic collision) instead of only the baseline, one where a subtle squash-and-stretch is applied to each letter proportional to its impact velocity, or one triggered by scroll position instead of firing on page load. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a headline where each letter drops in from above under simulated gravity and bounces to a stop, in plain HTML, CSS, and vanilla JavaScript — no physics or animation library, driven entirely by requestAnimationFrame.

Requirements:
- On page load, split the heading's text into one span per character (preserving spaces as non-breaking spaces in their own span so word gaps do not collapse), then position every letter above its natural resting position and hidden.
- For each letter independently, run a requestAnimationFrame loop that tracks a velocity and a vertical position, increasing velocity every frame by a gravity constant multiplied by the real elapsed time since the previous frame (not a fixed per-frame increment), and integrating that velocity into the position the same way.
- When a letter's simulated position reaches or passes its resting line, clamp it back to the resting line and reverse its velocity, multiplying the reversed velocity by a damping factor less than 1 so each bounce is measurably lower than the last, continuing until the velocity magnitude drops below a small threshold (or a maximum bounce count is reached), at which point the letter snaps exactly to its resting position and its loop stops.
- Stagger the start of each letter's fall using a per-letter delay proportional to its position in the string, so letters cascade in from left to right rather than all beginning to fall in the same frame.
- Include a replay button that resets every letter back above the line, invisible, and restarts the full staggered gravity-drop sequence for all letters from scratch.
- Play the sequence once automatically on page load in addition to being replayable via the button.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Watch it load, then replayLetters fall in with a bounce on page load. Click "Drop again" to reset every letter above the line and replay the cascade.
  2. 2
    Change the headline textEdit the text inside #heading in the HTML panel — buildLetters() re-splits it into per-letter spans on load.
  3. 3
    Adjust the fall strengthIn the JS panel, increase GRAVITY for a faster, snappier fall or decrease it for a slower, floatier one.
  4. 4
    Change how bouncy letters areUpdate BOUNCE_DAMPING (0 to 1) — closer to 1 means more energy is retained and letters bounce more times before settling.
  5. 5
    Adjust the stagger between lettersChange the 45 multiplier in animateDrop(l, i * 45) inside dropLetters() to speed up or slow down the cascade.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Playful brand and game landing pages
Gravity-driven letter drops fit playful, energetic brands — casual games, kids products, and consumer apps that want typography with a sense of physical fun.
Splash and loading screen headlines
Drop a wordmark or tagline into place as the first thing a user sees, giving the loading moment some visual interest instead of a static logo.
Learn requestAnimationFrame physics simulation
Edit GRAVITY and BOUNCE_DAMPING directly in the JS panel to see how tuning two constants changes the entire feel of a physics-driven animation.
Celebration and success-state messaging
Use on a "Success!" or "You won!" headline after a completed action — the bounce reinforces a rewarding, celebratory moment.
Pair with confetti or particle effects
Combine with a particle burst timed to each letter's final bounce for a more elaborate celebration sequence.
Interactive teaching or physics demos
Because the motion is a genuine (if simplified) gravity simulation, this doubles as a teaching example for velocity, acceleration, and energy loss on bounce.

Got questions?

Frequently Asked Questions

It is a genuine (simplified) simulation. Each letter has its own velocity that increases every frame by GRAVITY * dt, and position that increases every frame by velocity * dt — real numerical integration, not a value sampled from a fixed bezier curve. This is what allows the bounce to look organic rather than mechanically repeating.

When a letter's falling position reaches its resting line, its velocity is reversed and multiplied by BOUNCE_DAMPING (0.42), which is less than 1. Each bounce therefore launches the letter upward with less speed than it landed with, so peak height decreases every bounce until the velocity drops below a stop threshold.

Delta time (now - last) / 1000 measures real elapsed seconds between frames, so the fall speed stays consistent whether the display runs at 60Hz, 120Hz, or drops frames under load. A fixed per-frame step would make the animation run at different real-world speeds on different devices.

Staggering the start time with i * 45ms per letter index creates a cascading left-to-right rhythm. Each letter runs an independent physics loop with its own velocity and position state, so staggering starts never causes two letters to fight over the same animation state.

Increase BOUNCE_DAMPING toward 1 for more bounces retaining more energy each time; decrease it toward 0 for a fall that barely bounces at all and settles almost immediately on impact.

Yes. Click "JSX" for a React component. Store each letter's DOM node in a ref array, run the same requestAnimationFrame velocity loop inside a useEffect triggered by a "play" trigger prop, and clean up any pending animation frames on unmount.