Web Animations Medium
Kinetic Headline
Split a headline into words and letters with manual spans, then stagger them in with GSAP on scroll.
Open in Lab
MCP
html css gsap javascript
Targets: JS HTML
Code
[hidden] {
display: none !important;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Georgia", "Times New Roman", serif;
background: #111;
color: #faf8f5;
}
.stage {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
padding: 3rem 1.5rem;
max-width: 900px;
margin: 0 auto;
}
.label {
margin: 0 0 1rem;
font-family: system-ui, sans-serif;
font-size: 0.7rem;
letter-spacing: 0.28em;
text-transform: uppercase;
color: #c9a962;
}
.headline {
margin: 0;
font-size: clamp(2.5rem, 10vw, 5.5rem);
font-weight: 400;
line-height: 1.05;
letter-spacing: -0.03em;
}
.headline .word {
display: inline-block;
overflow: hidden;
margin-right: 0.2em;
vertical-align: top;
}
.headline .char {
display: inline-block;
transform: translateY(110%);
opacity: 0;
}
.sub {
margin: 1.5rem 0 0;
max-width: 24rem;
font-family: system-ui, sans-serif;
font-size: 1rem;
color: #9a9590;
line-height: 1.6;
}
.spacer {
height: 50vh;
}const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const headline = document.querySelector("[data-split]");
function splitText(el) {
const text = el.textContent.trim();
el.textContent = "";
text.split(/\s+/).forEach((word, wi) => {
const wordSpan = document.createElement("span");
wordSpan.className = "word";
word.split("").forEach((ch) => {
const charSpan = document.createElement("span");
charSpan.className = "char";
charSpan.textContent = ch;
wordSpan.append(charSpan);
});
el.append(wordSpan);
if (wi < text.split(/\s+/).length - 1) {
el.append(document.createTextNode(" "));
}
});
}
splitText(headline);
const chars = headline.querySelectorAll(".char");
gsap.registerPlugin(ScrollTrigger);
if (reduced) {
gsap.set(chars, { y: 0, opacity: 1 });
} else {
gsap.to(chars, {
y: 0,
opacity: 1,
duration: 0.65,
stagger: 0.03,
ease: "power3.out",
scrollTrigger: {
trigger: headline,
start: "top 70%",
toggleActions: "play none none reverse",
},
});
}<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kinetic Headline</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<section class="stage">
<p class="label">Editorial motion</p>
<h1 class="headline" data-split>Design in motion</h1>
<p class="sub">Letters rise into place as you scroll into view.</p>
</section>
<div class="spacer"></div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body>
</html>Kinetic Headline
No SplitText plugin — words and characters are wrapped in spans so staggered transforms stay licensing-friendly and copy-pasteable.