Velocity skew

Headlines lean into the direction of travel when you scroll fast and spring back when you stop.

How it works

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.

Full file
<!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>

More text effects