Word-by-word highlight
A pinned paragraph lights up one word at a time as you scroll through it.
Headlines lean into the direction of travel when you scroll fast and spring back when you stop.
Each frame the script measures how far the page moved since the last frame, eases a skew value toward that speed, and applies it as skewY. Because it eases every frame, the text settles smoothly instead of snapping.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Velocity skew</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
body { background: #ff5f1f; color: #160b05; }
.col { padding: 30vh 6vw 50vh; will-change: transform; }
.col h2 { font: 900 clamp(3.5rem, 15vw, 10rem)/.95 system-ui, sans-serif; text-transform: uppercase; letter-spacing: -.05em; margin: 0 0 .2em; }
</style>
</head>
<body>
<main class="col" id="col">
<h2>Fast</h2><h2>Faster</h2><h2>Lean in</h2><h2>Stop</h2><h2>Settle</h2><h2>Spring</h2><h2>Scroll</h2><h2>Again</h2>
</main>
<script>
const col = document.getElementById('col');
let lastY = scrollY, skew = 0;
function loop() {
const v = scrollY - lastY;
lastY = scrollY;
skew += (Math.max(-12, Math.min(12, v * 0.35)) - skew) * 0.12;
col.style.transform = 'skewY(' + skew.toFixed(2) + 'deg)';
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>