Falling Text with Gravity

As each line of a heading scrolls to the top of the viewport it shatters into individual characters that fall downward with randomised velocity, spin, and gravity — as if hitting a roof and crumbling off screen.

GSAPPhysics2DSplitTextScrollTriggerScroll

Setup — External Scripts

GSAP CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
Physics2DPlugin CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/Physics2DPlugin.min.js"></script>
ScrollTrigger CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js"></script>
SplitText CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/SplitText.min.js"></script>

Code

index.html
html
<div class="drop-wrapper">
  <div class="drop-section">
    <h1 data-drop-text="" class="drop-heading">This is just a<span data-drop-img="" class="drop-heading-img is--first"><img src="https://cdn.prod.website-files.com/681a615bf5a0f1ba3cb1ca38/681a62d0bb34b74d3514ecab_shape-squigle-1.png"></span>random quote<span data-drop-img="" class="drop-heading-img is--second"><img src="https://cdn.prod.website-files.com/681a615bf5a0f1ba3cb1ca38/681a62d0bb34b74d3514ecad_shape-squigle-2.png"></span>we used</h1>
  </div>
  <div class="drop-section">
    <h1 data-drop-text="" class="drop-heading">See how our window acts like<span data-drop-img="" class="drop-heading-img is--third"><img src="https://cdn.prod.website-files.com/681a615bf5a0f1ba3cb1ca38/681a62d0bb34b74d3514ecaf_shape-squigle-3.png"></span> a roof?</h1>
  </div>
  <div class="drop-section">
    <h1 data-drop-text="" class="drop-heading">So much fun!</h1>
  </div>
</div>
styles.css
css
.drop-wrapper {
  width: 100%;
  min-height: 350vh;
}

.drop-section {
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
  display: flex;
  position: relative;
}

.drop-heading {
  text-align: center;
  max-width: 40rem;
  margin-top: 0;
  margin-bottom: 0;
  font-size: 4rem;
  font-weight: 500;
  line-height: 1;
}

.drop-heading-img {
  z-index: 2;
  width: 1.2em;
  display: inline-block;
  position: relative;
}

.drop-heading-img img {
  width: 100%;
  max-width: 100%;
}

.drop-heading-img.is--first {
  transform: rotate(-20deg)translate(.15em, -.2em);
}

.drop-heading-img.is--second {
  transform: translate(-.1em, 0.2em)rotate(10deg);
}

.drop-heading-img.is--third {
  margin-left: -.1em;
  margin-right: -.1em;
  margin-top: -.2em;
  transform: translate(-.05em, .1em)rotate(50deg);
}

[data-drop-text] .line {
  display: block;
}
script.js
javascript
gsap.registerPlugin(ScrollTrigger, SplitText, Physics2DPlugin);

function initFallingTextGravity() {
  new SplitText("[data-drop-text]", {
    type: "lines, chars",
    autoSplit: true,
    linesClass: "line",
    onSplit(self) {
      let ctx = gsap.context(() => {
        self.lines.forEach(line => {
          gsap.timeline({
            scrollTrigger: {
              once: true,
              trigger: line,
              start: "top top-=10"
            }
          })
          .to(line.children, {
            duration: "random(1.5, 3)",
            physics2D: {
              velocity: "random(500, 1000)",
              angle: 90,
              gravity: 3000
            },
            rotation: "random(-90, 90)",
            ease: "none"
          })
          .to(line.children, {
            autoAlpha: 0,
            duration: 0.2
          }, "-=.2");
        });
      });

      return ctx;
    }
  });
}

document.addEventListener("DOMContentLoaded", () => {
  initFallingTextGravity();
});

Notes

  • `autoSplit: true` makes SplitText automatically revert and re-split on resize so lines re-wrap correctly; the `onSplit` callback returns a GSAP context so old animations are cleanly killed before new ones are created.
  • Each line gets its own ScrollTrigger with `once: true`, so the fall animation fires exactly once per line as it exits the top of the viewport.
  • Adjust `start: "top top-=10"` to tune the moment characters begin falling — increase the offset to trigger earlier, decrease it for later.
  • The second tween (`autoAlpha: 0`) fades characters out at the very end of the fall so they disappear cleanly rather than landing off-screen.

Guide

How it works

SplitText splits each `[data-drop-text]` element into lines and then characters. Each line becomes a ScrollTrigger — when it hits the top of the viewport (`start: "top top-=10"`), its characters launch downward with Physics2D.

Physics2D values

`velocity` — initial speed in px/s (randomised 500–1000 for natural variation). `angle` — 90° means straight down; try `random(80, 100)` for a slight spread. `gravity` — downward acceleration in px/s²; higher = faster fall.

Inline images

Decorative `<span data-drop-img>` elements placed inside the heading fall alongside the text characters because SplitText includes all child elements in `line.children`.