Overflow-Aware Marquee Scroller in Vanilla JS – fluid-marquee

Category: Animation , Javascript | May 27, 2026
Authorewanhowell5195
Last UpdateMay 27, 2026
LicenseMIT
Views0 views
Overflow-Aware Marquee Scroller in Vanilla JS – fluid-marquee

fluid-marquee is a Vanilla JavaScript marquee library that creates responsive horizontal and vertical scrolling content from simple HTML markup.

You can use the library to build a JavaScript marquee scroller for news tickers, logo strips, announcement bars, product ribbons, and compact content feeds.

Features:

  • Scrolls only when the content overflows the container.
  • Detects overflow before starting animation.
  • Horizontal and vertical scrolling.
  • Reverses direction through negative speed values.
  • Hover pause and click pause behavior.
  • Mouse and touch dragging.
  • Momentum after drag release.
  • Recalculates layout after resize and image load.
  • Pauses motion outside the viewport.
  • Runs constant scrolling on the compositor thread.
  • JavaScript controls for dynamic items.

How to use it:

1. Install fluid-marquee with NPM and import the CSS and JavaScript in your application entry point:

# NPM
$ npm install fluid-marquee
import "fluid-marquee/styles.css";
import "fluid-marquee";

2. Or load the required files directly in your HTML document.

<!-- Local -->
<link rel="stylesheet" href="./dist/fluid-marquee.min.css">
<script src="./dist/fluid-marquee.min.js"></script>
<!-- Or from a CDN -->
<script src="
https://cdn.jsdelivr.net/npm/fluid-marquee/dist/fluid-marquee.min.js
"></script>
<link href="
https://cdn.jsdelivr.net/npm/fluid-marquee/dist/fluid-marquee.min.css
" rel="stylesheet">

3. Place any content you want to scroll inside a container with the class fluid-marquee. Each item needs the class fluid-marquee-item. The library auto-initializes every .fluid-marquee on the page as soon as the DOM is ready. No JavaScript call is required.

<div class="fluid-marquee" aria-label="Featured frontend tools">
  <div class="fluid-marquee-item">Canvas Inspector</div>
  <div class="fluid-marquee-item">CSS Grid Helper</div>
  <div class="fluid-marquee-item">UI Token Builder</div>
  <div class="fluid-marquee-item">Form State Debugger</div>
</div>

4. Product ribbons often need different motion speeds for different page sections. A positive speed moves in the default direction, and a negative speed reverses the track.

<div class="fluid-marquee" data-fluid-marquee-speed="72">
  <div class="fluid-marquee-item">Starter Plan</div>
  <div class="fluid-marquee-item">Team Plan</div>
  <div class="fluid-marquee-item">Agency Plan</div>
</div>
<div class="fluid-marquee" data-fluid-marquee-speed="-120">
  <div class="fluid-marquee-item">Free Updates</div>
  <div class="fluid-marquee-item">MIT License</div>
  <div class="fluid-marquee-item">No jQuery Required</div>
</div>

5. Vertical scrollers fit compact announcement cards and sidebar update panels. The container needs a height because the library measures vertical overflow.

<div
  class="fluid-marquee"
  data-fluid-marquee-vertical
  data-fluid-marquee-speed="48"
  style="height: 260px;"
>
  <div class="fluid-marquee-item">Build queue finished</div>
  <div class="fluid-marquee-item">Threeed review</div>
  <div class="fluid-marquee-item">New customer signup received</div>
</div>

6. Interactive marquees need a clear pause model for links, buttons, and readable content. The pausable attribute combines hover pause with click-locked pause.

<div class="fluid-marquee" data-fluid-marquee-pausable>
  <a class="fluid-marquee-item" href="/tools/chart-builder">Chart Builder</a>
  <a class="fluid-marquee-item" href="/tools/icon-browser">Icon Browser</a>
  <a class="fluid-marquee-item" href="/tools/color-checker">Color Checker</a>
</div>

7. Client-rendered pages may add the marquee after an API request. Initialize the new container after the DOM node exists.

// Create the marquee after async data arrives.
const updatesPanel = document.querySelector("#release-updates");
updatesPanel.innerHTML = `
  <div class="fluid-marquee" data-fluid-marquee-speed="80">
    <div class="fluid-marquee-item">v2.1.0 released</div>
    <div class="fluid-marquee-item">New theme tokens added</div>
    <div class="fluid-marquee-item">Keyboard fixes shipped</div>
  </div>
`;
// Initialize all uninitialized marquees inside this panel.
FluidMarquee.initAll(updatesPanel);

Configuration Options:

All options are available as data- attributes on the HTML element or as camelCase keys in the JavaScript options object passed to FluidMarquee.init().

  • speed (number): Scroll speed in pixels per second. Defaults to 64. Negative values scroll in reverse.
  • infinite (boolean): Force scrolling at all times, even when content fits inside the container.
  • vertical (boolean): Scroll vertically. The container must have an explicit height.
  • pausable (boolean): Pause on hover and lock-pause on click. Sets both pauseHover and pauseClick at once.
  • pauseHover (boolean): Pause while the pointer is over the marquee. Resumes automatically on pointer leave.
  • pauseClick (boolean): Lock-pause on click inside the marquee. Click outside to resume.
  • draggable (boolean): Accept mouse and touch drag to scrub through the marquee. Applies momentum on release.
  • runScripts (boolean): Re-execute <script> tags inside cloned items. Off by default.

API Methods:

// Get the instance from the element after auto-init
const m = document.querySelector(".fluid-marquee").marquee;
// Sticky pause - only resume() clears this state
m.pause();
// User-style pause - clicking outside the marquee also clears this
m.pause(false);
// Clear api and click pauses (hover and drag self-resolve)
m.resume();
// Force a re-measure (ResizeObserver handles most cases automatically)
m.refresh();
// Tear down the marquee and restore items as direct children of the container
m.destroy();
// Append one item
m.add(itemEl);
// Append several items at once
m.add(itemA, itemB, itemC);
// Remove one or more items
m.remove(itemEl);
// Replace all items at once
m.setItems([newItemA, newItemB, newItemC]);
// Read the current item elements as an array
console.log(m.items);
// Read pause state flags
console.log(m.paused);      // True if anything keeps the marquee paused
console.log(m.apiPaused);   // True if paused via m.pause()
console.log(m.userPaused);  // True if hoverPaused || clickPaused || dragPaused
console.log(m.hoverPaused); // True while the pointer hovers
console.log(m.clickPaused); // True when locked via click-pause
console.log(m.dragPaused);  // True while the user drags
// Initialize a specific element (walks up with closest, idempotent)
FluidMarquee.init(el, { speed: 80, pausable: true });
// Initialize all uninitialised marquees within a root element
FluidMarquee.initAll(document, { draggable: true });
// Get the instance for an element or any of its descendants
FluidMarquee.get(el);

Events.

const el = document.querySelector(".fluid-marquee");
// Fires on the element once it finishes initialising
el.addEventListener("fluid-marquee:init", function (e) {
  console.log("Initialised:", e.target.marquee);
});
// Fires on window once the initial auto-init pass completes after DOMContentLoaded.
// Use this when your script runs before the library has had a chance to initialize.
addEventListener("fluid-marquee:ready", function () {
  document.querySelector(".fluid-marquee").marquee.pause();
});
// Fires when a pause cause activates, as long as no higher-priority cause is already active.
// e.detail.cause is one of: "api" | "click" | "drag" | "hover"
// Priority order: api > click > drag > hover
el.addEventListener("fluid-marquee:pause", function (e) {
  if (e.detail.cause === "hover") return; // ignore hover-only pauses
  document.getElementById("status").textContent = "Paused";
});
// Fires when a pause cause deactivates
el.addEventListener("fluid-marquee:resume", function (e) {
  if (e.detail.cause === "hover") return;
  document.getElementById("status").textContent = "Playing";
});

Alternatives:

FAQs:

Q: Why is my marquee not scrolling?
A: The content may fit inside the container. fluid-marquee stays static by default in that case. Add data-fluid-marquee-infinite if you want motion at all widths.

Q: Can I use fluid-marquee with React or Vue?
A: Yes. Render the marquee container after the component mounts, then initialize it from JavaScript. Use the item API for dynamic changes instead of replacing the children through reactive rendering.

Q: Does fluid-marquee require jQuery?
A: No. It uses modern JavaScript, CSS, ResizeObserver, IntersectionObserver, Web Animations API, and requestAnimationFrame.

Q: Why do my item styles stop working after initialization?
A: The library wraps your original children inside internal elements. Replace direct-child selectors with .fluid-marquee-item or a descendant selector.

Q: How should I handle buttons or links inside cloned marquee items?
A: Use event delegation on the marquee container. A single listener can handle clicks from the original items and their cloned copies.

You Might Be Interested In:


Leave a Reply