Skip to content

A time-synchronized marquee engine for multi-monitor displays. Each browser window renders an identical marquee animation, staying perfectly in sync using a shared clock via BroadcastChannel.

Notifications You must be signed in to change notification settings

MonksterFX/marqueex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MarqueeX

A time-synchronized marquee engine for multi-monitor displays. Each browser window renders an identical marquee animation, staying perfectly in sync using a shared clock via BroadcastChannel.

Table of Contents

Features

  • Time-based synchronization - All windows compute position from a shared start time
  • Multi-monitor support - Configure window index and width for seamless cross-monitor continuity
  • No framework dependencies - Pure ES modules with zero runtime dependencies
  • Shadow DOM encapsulation - Styles don't leak, content is cleanly isolated
  • ResizeObserver integration - Automatically adapts to content changes
  • Reduced motion support - Respects prefers-reduced-motion user preference
  • Fallback support - Uses localStorage events when BroadcastChannel is unavailable
  • TypeScript-friendly - Full JSDoc type annotations included

Installation

# Clone the repository
git clone <repo-url>
cd marqueex

# Install dependencies
npm install

# Run the demo
npm run start
# Open http://localhost:3000/demo/

Or import directly in your project:

import { MarqueeEngine } from './src/index.js';

Quick Start

Basic Usage

<div id="marquee">
  <span>Your scrolling content here</span>
</div>

<script type="module">
  import { MarqueeEngine } from './src/index.js';

  const engine = new MarqueeEngine({
    container: document.getElementById('marquee'),
    speed: 100,  // pixels per second
    gap: 50,     // gap between content repetitions
    sync: true   // enable cross-window sync
  });

  engine.start();
</script>

Multi-Monitor Configuration

For true multi-monitor continuity, assign each window a position index:

// Window on leftmost monitor
const engine = new MarqueeEngine({
  container: document.getElementById('marquee'),
  windowIndex: 0,      // 0 = leftmost position
  windowWidth: 1920    // width of each monitor in pixels
});

// Window on second monitor (opened separately)
const engine = new MarqueeEngine({
  container: document.getElementById('marquee'),
  windowIndex: 1,      // 1 = second from left
  windowWidth: 1920
});

Multi-Monitor Setup

Automatic Sync (Same Content)

  1. Open the demo page in your first browser window
  2. Click "Open New Window" or manually open the same URL in additional windows
  3. Position each window on a different monitor
  4. All marquees will animate in sync showing the same content

Continuous Scrolling (Different Offsets)

For a seamless effect where content appears to scroll from one monitor to the next:

  1. Configure each window with its windowIndex (0, 1, 2, etc. from left to right)
  2. Set windowWidth to match your monitor resolution
  3. Each window will show a different portion of the scrolling content
// Example: 3-monitor setup with 1920px monitors
const params = new URLSearchParams(window.location.search);
const windowIndex = parseInt(params.get('monitor') || '0');

const engine = new MarqueeEngine({
  container: document.getElementById('marquee'),
  windowIndex: windowIndex,
  windowWidth: 1920
});

Note: True pixel-perfect continuity across monitors depends on DPI, zoom levels, and window positioning. This library provides time-based synchronization which creates a visually convincing effect.

API Reference

MarqueeEngine

The main animation controller.

Constructor Options

Option Type Default Description
container HTMLElement required The marquee container element
speed number 100 Animation speed in pixels per second
gap number 50 Gap between content repetitions in pixels
sync boolean true Enable cross-window synchronization
resyncInterval number 0 Resync broadcast interval in ms (0 = disabled)
respectReducedMotion boolean true Respect prefers-reduced-motion
windowIndex number 0 Position of this window (0 = leftmost, 1 = next, etc.)
windowWidth number 0 Width of each monitor in pixels (0 = auto-detect from viewport)

Methods

Method Description
start() Starts the animation and broadcasts to other windows
stop() Stops the animation and broadcasts to other windows
sync(startTime) Manually syncs to a specific start time (ms)
destroy() Cleans up all resources (observers, channels, timers)
engine.start();           // Start animation
engine.stop();            // Stop animation
engine.sync(startTime);   // Sync to timestamp
engine.destroy();         // Clean up

Properties

Property Type Access Description
speed number get/set Animation speed in pixels per second
gap number get/set Gap between content repetitions in pixels
windowIndex number get/set Position index of this window (0-based)
windowWidth number get/set Monitor width in pixels (0 = auto)
running boolean get Whether the animation is currently running
isAuthority boolean get Whether this instance is the time authority
startTime number get Current shared start time in milliseconds
reducedMotionActive boolean get Whether reduced motion preference is active
// Reading properties
console.log(engine.running);       // true/false
console.log(engine.isAuthority);   // true/false

// Setting properties (automatically broadcasts to other windows)
engine.speed = 150;       // Change speed
engine.gap = 100;         // Change gap
engine.windowIndex = 2;   // Change window position
engine.windowWidth = 1920; // Set monitor width

SyncChannel

Low-level BroadcastChannel wrapper for custom synchronization needs.

Constructor

const channel = new SyncChannel(channelName);
Parameter Type Default Description
channelName string 'marquee-sync' Name of the broadcast channel

Methods

Method Description
broadcast(message) Sends a message to all other windows
onMessage(callback) Registers a callback for incoming messages
requestSync() Requests sync from existing instances
destroy() Closes the channel and cleans up

Properties

Property Type Description
isUsingFallback boolean Whether localStorage fallback is active

Message Types

The SyncChannel uses these message types internally:

Type Description
INIT Initial sync message with start time and config
RESYNC Periodic resync from the authority
REQUEST_SYNC Request sync from existing instances
CONFIG Configuration change (speed, gap, windowWidth)
PLAYBACK Playback state change (start/stop)

Example: Custom Synchronization

import { SyncChannel, getCurrentTime } from './src/index.js';

const channel = new SyncChannel('my-custom-channel');

// Listen for messages
channel.onMessage((msg) => {
  console.log('Received:', msg.type, msg);
  
  if (msg.type === 'INIT') {
    // Sync to the shared start time
    myAnimation.setStartTime(msg.startTime);
  }
});

// Request sync from existing instances
channel.requestSync();

// Or become the authority and broadcast
channel.broadcast({
  type: 'INIT',
  startTime: getCurrentTime(),
  speed: 100,
  gap: 50
});

// Check if using localStorage fallback
if (channel.isUsingFallback) {
  console.log('BroadcastChannel not available, using localStorage');
}

// Clean up when done
channel.destroy();

getCurrentTime

Returns the current high-precision timestamp using performance.timeOrigin + performance.now().

import { getCurrentTime } from './src/index.js';

const timestamp = getCurrentTime(); // e.g., 1706745600000.123

How It Works

Synchronization Model

  1. The first instance becomes the time authority and broadcasts an INIT message with the start time
  2. New instances send REQUEST_SYNC and wait for an INIT response
  3. All instances compute animation offset using the same formula:
// Base offset from time
offset = ((currentTime - startTime) / 1000) * speed

// Add window position offset for multi-monitor continuity
totalOffset = offset + (windowIndex * windowWidth)

// Wrap for seamless looping
wrappedOffset = totalOffset % contentWidth

This makes synchronization deterministic without per-frame messaging.

Architecture

┌─────────────────────┐     ┌─────────────────────┐
│    Window 1         │     │    Window 2         │
│    (windowIndex: 0) │     │    (windowIndex: 1) │
│  ┌───────────────┐  │     │  ┌───────────────┐  │
│  │ MarqueeEngine │  │     │  │ MarqueeEngine │  │
│  │  (Authority)  │  │     │  │   (Follower)  │  │
│  └───────┬───────┘  │     │  └───────┬───────┘  │
│          │          │     │          │          │
│  ┌───────▼───────┐  │     │  ┌───────▼───────┐  │
│  │   RAF Loop    │  │     │  │   RAF Loop    │  │
│  │ offset + 0px  │  │     │  │ offset+1920px │  │
│  └───────┬───────┘  │     │  └───────┬───────┘  │
└──────────┼──────────┘     └──────────┼──────────┘
           │                           │
           │  ┌─────────────────────┐  │
           └──│  BroadcastChannel   │──┘
              │ (startTime, config) │
              └─────────────────────┘

Message Flow

Window 1 (first to load)          Window 2 (loads later)
         │                                │
         │ ◄──── REQUEST_SYNC ────────────┤
         │                                │
         ├────── INIT ───────────────────►│
         │       (startTime, speed,       │
         │        gap, running)           │
         │                                │
         │ ◄──── CONFIG ──────────────────┤
         │       (user changes speed)     │
         │                                │

Browser Support

Browser Version Notes
Chrome 54+ Full support
Edge 79+ Full support
Firefox 38+ Full support
Safari 15.4+ Full support
Older browsers - Falls back to localStorage events

The library automatically falls back to localStorage events for older browsers or when BroadcastChannel is unavailable (e.g., private browsing mode in some browsers).

Limitations

  • No monitor detection - Cannot detect monitor arrangement or geometry; windowIndex must be set manually
  • Pixel alignment - Exact pixel alignment depends on DPI, zoom, and window positioning
  • Same origin only - Cross-device synchronization is not supported (requires same origin)
  • No SSR - Requires DOM (browser environment only)
  • Clock drift - Very long-running animations may experience minor drift between windows

License

MIT

About

A time-synchronized marquee engine for multi-monitor displays. Each browser window renders an identical marquee animation, staying perfectly in sync using a shared clock via BroadcastChannel.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published