Scroll Velocity Marquee — Free HTML CSS JS Snippet

Scroll Velocity Marquee · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Scroll-velocity reactive
Speed scales with frame-to-frame scrollY delta.
Momentum skew
skewX leans the letters with the scroll, clamped to ±18°.
Exponential ease-out
Velocity decays by 0.9 each frame after you stop.
Baseline drift
Rows keep moving even when the page is still.
Opposing rows
data-dir scrolls and skews the two rows opposite ways.
Seamless loop
Width-based wrapping with no visible seam.
Frame-rate independent
A dt factor keeps speed consistent across displays.
Outline + fill type
-webkit-text-stroke alternates hollow and solid words.

About this UI Snippet

Scroll Velocity Marquee — Speed and Skew Driven by Scrolling

Screenshot of the Scroll Velocity Marquee snippet rendered live

The scroll-velocity marquee is the kinetic-typography effect seen on award-winning agency and portfolio sites: rows of giant words drift slowly on their own, but the moment you scroll, they surge in the scroll direction and skew with the momentum — then ease back to a calm baseline when you stop. This snippet implements the whole behavior in plain HTML, CSS, and vanilla JavaScript with no scroll library.

Measuring scroll velocity

A passive scroll listener computes velocity as the simple frame-to-frame delta of window.scrollY: how many pixels the page moved since the last event. Scrolling down fast yields a large positive value; scrolling up yields a negative one. This raw velocity feeds the animation, so the marquee responds to both how fast and which way you scroll. The listener is marked { passive: true } so it never blocks the browser's scrolling.

A baseline drift plus a boost

The animation runs in a continuous requestAnimationFrame loop. Each frame, the track moves by a constant BASE speed so it always drifts even when the page is still, plus a boost proportional to the absolute scroll velocity, plus a direct velocity term that shoves it in the scroll direction. Crucially, the stored velocity is multiplied by 0.9 every frame, so after you stop scrolling it decays exponentially toward zero — the surge eases out naturally instead of stopping dead.

The skew that sells the motion

Speed alone looks mechanical; the effect comes alive because the track also applies skewX. The skew is computed from velocity and clamped to ±18° so fast scrolls visibly lean the letters like they're being dragged through the air, while a still page sits at 0°. Each row's skew is multiplied by its direction so the two rows lean opposite ways, reinforcing the sense of momentum.

Opposing rows

The two rows read a data-dir of 1 and -1, so they scroll and skew in opposite directions. This counter-motion is what makes the section feel dynamic rather than like a single conveyor belt. The same loop drives both; only the direction multiplier differs.

Seamless looping and frame-rate independence

Each track is filled with several repetitions of the word list, and its loop width is measured as scrollWidth / 2. When the offset passes that width in either direction, it wraps by adding or subtracting the width, landing on an identical frame so there's no visible seam. A dt factor normalizes each frame against a 16.67ms baseline (and is capped at 3) so the speed stays consistent across refresh rates and survives the occasional dropped frame.

Type styling

The words alternate between outline and filled styles using -webkit-text-stroke — odd items are hollow purple outlines, even items are solid lavender — with a cyan dot separating each. Horizontal mask-image gradients fade both ends so words enter and exit softly. All of this is pure CSS; the JavaScript only sets transform.

Customizing it

Tune BASE for a faster or slower resting drift, raise the 0.25 boost factor for a more violent reaction, change the 0.9 decay for a longer or snappier ease-out, or widen the skew clamp for more dramatic lean. Swap the WORDS array for your own slogan, add more rows with alternating data-dir, and recolor the stroke and fill. Pair it with a logo marquee or a lamp header for a bold landing section.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to reverse-engineer the velocity decay or the seamless-loop wrapping math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the stored velocity is multiplied by 0.9 every frame instead of being reset immediately after each scroll event, and how the dt normalization keeps the drift speed consistent across different refresh rates. The same assistant can help optimize it — for example checking whether the scrollWidth measurement inside the requestAnimationFrame loop's fallback branch could cause layout thrashing on very long word lists, or whether the skew clamp and boost multiplier need retuning for touch-scroll devices that report much larger scrollY deltas per event. It is just as useful for extending the effect: ask it to add a third row with a different base speed, tie the boost to horizontal trackpad gestures as well as vertical scroll, or pause the drift entirely when the marquee scrolls out of the viewport using IntersectionObserver. 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 "scroll velocity marquee" in plain HTML, CSS, and vanilla JavaScript only — no animation library, no build step.

Requirements:
- Two or more horizontal rows of repeating words, each row's track built by duplicating a word list several times so it can loop seamlessly, with a mask-image gradient fading the left and right edges of each row.
- Track scroll velocity as the frame-to-frame delta of window.scrollY inside a passive scroll event listener — do not use any external velocity or physics library.
- Run a continuous requestAnimationFrame loop, independent of the scroll event, that on every frame: decays the stored velocity by multiplying it by a constant just under 1 (so it exponentially approaches zero after scrolling stops rather than snapping back), computes a speed boost proportional to the absolute decayed velocity, and adds a constant baseline drift speed so each row keeps moving even when the page is completely still.
- Apply a skewX transform to each row's track derived from the current velocity and clamped to a fixed maximum angle in both directions, so fast scrolling visibly leans the text and a still page sits at zero skew.
- Give each row an opposite direction multiplier (for example +1 and -1) so the rows drift and skew in opposite directions for a counter-motion effect, driven by the same shared loop.
- Implement the loop wrapping so that once a row's horizontal offset exceeds half its total scrollWidth in either direction, it wraps by adding or subtracting that half-width, landing on a visually identical frame with no seam or jump.
- Normalize the per-frame movement against elapsed time (delta time since the last frame, capped at a reasonable maximum) so the animation speed does not vary with the display's refresh rate.

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
    Paste HTML, CSS, and JSTwo rows of giant words drift slowly in opposite directions.
  2. 2
    Scroll the pageThe words surge in the scroll direction and skew with the momentum.
  3. 3
    Stop scrollingThe boost decays and the rows ease back to a calm baseline drift.
  4. 4
    Scroll upVelocity goes negative and the rows reverse and lean the other way.
  5. 5
    Edit the wordsReplace the WORDS array with your own slogan.
  6. 6
    Tune the feelAdjust BASE, the boost factor, decay, and skew clamp.

Real-world uses

Common Use Cases

Agency landing pages
A kinetic band above a feature tabs showcase.
Portfolio sites
Between a portfolio hero and the work grid.
Brand statements
Loop slogans like a giant marquee with motion.
Event microsites
Pair with a lamp header for drama.
Product launches
Reinforce a tagline near an animated gradient CTA.
Kinetic type demos
A reference for scroll-driven velocity and skew.

Got questions?

Frequently Asked Questions

A passive scroll listener stores the difference between the current and previous window.scrollY each time it fires. That delta is the velocity — large and positive when scrolling down fast, negative when scrolling up. It feeds both the speed boost and the skew, so the marquee reacts to how fast and which way you scroll.

The stored velocity is multiplied by 0.9 every animation frame, so it decays exponentially toward zero once new scroll events stop arriving. That turns an abrupt stop into a natural ease-out where the words coast back to the constant BASE drift instead of halting instantly.

Each frame the track applies skewX in addition to translateX. The skew is derived from velocity and clamped to ±18 degrees, and multiplied by each row's direction, so fast scrolls visibly lean the giant letters as if dragged through the air, while a still page sits at zero skew.

Yes. Each track repeats the word list several times and its loop width is scrollWidth / 2. When translateX passes that width in either direction it wraps by adding or subtracting the width, landing on an identical frame, so the loop is seamless. Horizontal mask gradients also fade both ends.

Run the rAF loop in a mount effect and store the frame id to cancel on unmount. Keep the latest velocity in a ref (not state) so updating it doesn't re-render every frame. Attach the passive scroll listener in the same effect with cleanup. Use refs for the track elements. The CSS, including text-stroke and the mask gradients, ports directly to Tailwind arbitrary values.