Text Animations

Velocity Marquee

An infinitely looping marquee that speeds up with scroll velocity and briefly reverses when you scroll back quickly.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add velocity-marquee.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

5
Active edited usageDefault props
<VelocityMarquee/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/velocity-marquee.en.json

Usage

children can be anything. Remember to leave trailing spacing (such as pr-8 or a trailing separator) so the loop is seamless:

import { VelocityMarquee } from "@/components/ui/velocity-marquee";

<VelocityMarquee baseVelocity={5}>
  <span className="pr-8 text-5xl font-bold">
    WebberUI — Animate everything ·
  </span>
</VelocityMarquee>

It also works for a logo row, with a negative baseVelocity to scroll the other way:

<VelocityMarquee baseVelocity={-3}>
  <div className="flex items-center gap-12 pr-12">
    <LogoA className="h-8" />
    <LogoB className="h-8" />
    <LogoC className="h-8" />
  </div>
</VelocityMarquee>

Inside a nested scroll container

By default the component tracks window scroll. When it sits inside a fixed-height overflow-y-auto container, pass that container's ref to container. The speed-up comes from the container's scroll velocity instead of the window's.

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[400px] overflow-y-auto">
  <VelocityMarquee container={scrollerRef}>{/* content */}</VelocityMarquee>
</div>

When the window itself is the scroll container, container can be omitted and behavior is unchanged.

Props

PropTypeDefaultDescription
childrenReactNode—The marquee content; four copies are rendered internally for a seamless loop
baseVelocitynumber5Base speed (percent of the content width moved per second); negative reverses
direction"left" | "right""left"Travel direction; "right" is equivalent to negating baseVelocity
containerRefObject<HTMLElement>—Ref of the nested scroll container; scroll velocity is read from it too. Omit to use the window as the scroll container
classNamestring—Applied to the outer container

How it works

  • Scroll-driven speed: useScroll().scrollY → useVelocity gives the scroll speed (px/s) → useSpring smooths it → it is mapped to a speed multiplier, and useAnimationFrame accumulates the offset frame by frame. The faster the page scrolls, the faster the marquee runs; once scrolling stops, the spring eases it back to the base speed
  • Scrolling back quickly makes the speed multiplier negative, so the marquee briefly reverses direction before recovering
  • The offset wraps between -25% and 0% through a hand-written wrap function, with four copies of the content rendered so it is always visually seamless. A single copy should be at least a third of the container width to avoid exposing blank space
  • The animation only touches transform and never triggers layout; copies 2 through 4 are marked aria-hidden, so the screen reader only reads one
  • When the user has "reduce motion" enabled at the system level, it degrades to a single static copy that does not scroll