webdevpuneet.com

Grab-to-Pause Auto-Scroll Ticker — HTML CSS JS Snippet

Grab-to-Pause Auto-Scroll Ticker · Carousels · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="ast-wrap" id="astWrap">
  <div class="ast-track" id="astTrack">
    <div class="ast-chip" style="background:linear-gradient(160deg,#6366f1,#4338ca)">React</div>
    <div class="ast-chip" style="background:linear-gradient(160deg,#0ea5e9,#0369a1)">Vue</div>
    <div class="ast-chip" style="background:linear-gradient(160deg,#ec4899,#9d174d)">Angular</div>
    <div class="ast-chip" style="background:linear-gradient(160deg,#10b981,#047857)">Svelte</div>
    <div class="ast-chip" style="background:linear-gradient(160deg,#f59e0b,#b45309)">Tailwind</div>
    <div class="ast-chip" style="background:linear-gradient(160deg,#8b5cf6,#5b21b6)">GSAP</div>
  </div>
  <p class="ast-hint">Auto-scrolls continuously — grab and drag to scrub, release to resume.</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Seamless infinite auto-scroll from a finite chip set, via a single duplicated copy and a wrapped position
One shared position variable, written by either the autoplay loop or the active drag — never both at once
Grabbing mid-scroll takes over instantly; releasing resumes autoplay from precisely that same position
Correct negative-safe modulo wrapping, so dragging backward past the start still loops cleanly
Unified pointer and touch handling for the grab-and-drag gesture
Loop width recalculated on resize, keeping the seamless wrap accurate at any container size

About this UI Snippet

Grab-to-Pause Auto-Scroll Ticker — One Position Variable, Two Different Writers

Screenshot of the Grab-to-Pause Auto-Scroll Ticker snippet rendered live

A typical infinite ticker either auto-scrolls *or* is draggable — rarely both at once, seamlessly. This one keeps a single pos variable that two completely different code paths are allowed to write to: a requestAnimationFrame loop advances it automatically by SPEED pixels per millisecond when nobody's touching it, and a pointer/touch drag handler overwrites it directly with the cursor's position when someone is. Because both paths write to the *exact same variable*, grabbing the ticker mid-scroll and releasing it resumes autoplay from precisely wherever the drag left off — there's no separate "resume point" to calculate.

The loop trick: duplicate once, wrap the position

To scroll infinitely from a *finite* set of chips, every chip is cloned once and appended after the originals — so the track visually contains two identical copies back to back. apply() then wraps pos into the range [0, loopWidth) with ((pos % loopWidth) + loopWidth) % loopWidth (the double-modulo handles negative pos correctly, which plain % in JavaScript doesn't). The instant pos would scroll past one full copy's width, the wrap makes it jump back to the equivalent position in the first copy — and because the second copy is pixel-identical to the first, that jump is completely invisible.

Autoplay pauses itself simply by not advancing

There's no explicit "pause" state variable — the tick() loop just checks if (!dragging) before adding to pos at all. While dragging is true, the rAF loop keeps running (so it's instantly ready to resume) but skips the auto-advance line entirely, letting the drag handler be the only thing touching pos for that whole gesture.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA row of technology chips scrolls continuously and smoothly to the left, looping seamlessly forever.
  2. 2
    Click and hold anywhere on the tickerAuto-scrolling stops instantly and the ticker follows your cursor precisely.
  3. 3
    Drag left or rightScrub through the chips manually in either direction, well beyond the normal auto-scroll range.
  4. 4
    ReleaseAuto-scrolling resumes immediately from exactly wherever you released it — no jump or reset.
  5. 5
    Add a seventh chipAdd one more .ast-chip element — the clone-and-loop logic picks it up automatically on the next page load.

Real-world uses

Common Use Cases

Technology or client logo tickers
A classic "as seen in" or tech-stack strip that a curious visitor can also grab and browse manually.
Skill or tag showcases on a portfolio
An always-moving accent that remains genuinely interactive rather than purely decorative.
Announcement or promo tickers
Continuously scroll several short messages while still letting a user pause and read one manually.
Category or filter chip rails
An eye-catching idle animation for a chip row that becomes a normal draggable list the moment it's touched.

Got questions?

Frequently Asked Questions

Change the SPEED constant (pixels per millisecond) — a larger number scrolls faster, a smaller one slower. Since it's time-based rather than frame-based, the speed stays consistent regardless of the display's refresh rate.

loopWidth is measured directly from the real rendered widths of the original chips (plus their gaps) on every measure() call — it doesn't assume equal widths, so any mix of chip sizes still wraps at exactly the right pixel offset.

Resetting position abruptly (position = 0) would produce a visible jump the instant the reset happens. Having a second, identical copy immediately following the first means the wrap-around lands on pixel-identical content, so the "reset" is invisible.

Yes — negate the SPEED constant, or flip the sign in the tick() function's pos += line, to reverse the automatic scroll direction; dragging is unaffected either way since it directly follows the cursor.

Continuous motion can be distracting or disorienting for some users — add a prefers-reduced-motion check that skips starting the requestAnimationFrame loop (leaving the ticker static but still draggable) for users who've requested reduced motion.