ledding
v2.1.1
Published
High-performance LED matrix animation library for Canvas 2D
Maintainers
Readme
Ledding
High-performance LED matrix animation library for Canvas 2D.
30.0KB minified | 9.0KB gzipped | Zero runtime dependencies
Features
- Smooth LED animations with customizable transitions
- Infinite scroll in 8 directions (cardinal + diagonal)
- Cascade, wave, interlaced, and random ignition/extinction patterns
- Multiple LED intensities with color interpolation
- Sparse grid optimization (only renders active LEDs)
- Color caching for minimal GC pressure
- Exact visual bounds with symmetric alignment and margin calculations
- Sharp HiDPI rendering with configurable pixel ratio
- Fractional CSS-pixel sizing and container-aware resize observation
- Ragged pattern support (the longest row defines the layout width)
- Full TypeScript support with tree-shaking
- No memory leaks - proper cleanup on destroy
Installation
npm install leddingQuick Start
import { Ledding, CircleRenderer, CenterAligner, Directions, Pattern } from 'ledding';
const pattern = [
[0, 1, 1, 0],
[1, 2, 2, 1],
[1, 2, 2, 1],
[0, 1, 1, 0],
];
const ledding = new Ledding('#container', {
ledSize: 20,
ledGap: 4,
artPattern: pattern,
renderer: CircleRenderer,
aligner: CenterAligner,
animation: {
scroll: {
direction: Directions.TO_LEFT,
speed: 80
}
}
});
// Pause/Resume
ledding.pause();
ledding.resume();
// Change pattern dynamically
ledding.setPattern(newPattern);
// Clean up
ledding.destroy();Browser (UMD)
<script src="https://unpkg.com/ledding/dist/ledding.umd.min.js"></script>
<script>
const { Ledding, CircleRenderer, CenterAligner } = window.Ledding;
const instance = new Ledding.Ledding('#container', {
// options
});
</script>Configuration
interface LeddingOptions {
// Base dimensions
ledSize: number; // LED size in pixels (default: 20)
ledGap: number; // Gap between LEDs (default: 4)
scaleToFit: boolean; // Auto-scale to fit container (default: true)
pixelRatio: number | 'auto'; // HiDPI bitmap scale (default: 'auto')
// Content
artPattern: number[][]; // 2D array pattern (0 = off, 1-N = on states)
// Strategies (tree-shakeable)
renderer: Renderer; // CircleRenderer | SquareRenderer
aligner: Aligner; // CenterAligner, TopLeftAligner, etc.
// Colors
colors: {
background: string | null; // null for transparent
base: string; // Base LED color (off state)
states: Record<number, string>; // Colors for each state (1, 2, 3...)
};
// Opacities
opacities: {
base: { min: number; max: number }; // Random range for base LEDs
active: number; // Opacity when active
};
// Performance
fps: number; // Target frame rate (default: 20)
// Animation
animation: {
scroll: {
direction: Direction; // 'to-left', 'to-right', etc.
speed: number; // Pixels per second
};
ignition: {
pattern: AnimationPattern; // 'cascade', 'wave', 'interlaced', 'random'
direction: Direction;
delay: number; // Frames between steps
step: number; // Group size for patterns
};
extinction: {
// Same as ignition
};
};
// Transition speeds (lerp factor)
transitions: {
ignition: { min: number; max: number; randomize: boolean };
extinction: { min: number; max: number; randomize: boolean };
morph: { min: number; max: number; randomize: boolean };
};
// Grid mode
grid: {
fill: boolean; // true = classic (all LEDs), false = sparse (optimized)
lifespan: number; // Frames before removing inactive LED (sparse mode)
};
}Interrupted LED transitions
Color, size, and opacity interpolate from their current values when a moving LED
encounters another pattern state. Active-to-active changes use transitions.morph.
An unfinished ignition keeps its remaining row delay and, once started, its
original completion time when only the active target changes.
If a LED leaves the pattern before ignition starts, that pending ignition is
cancelled. If it has already become visible, it holds its current appearance
through the extinction delay, then fades out using transitions.extinction.
Sparse LEDs remain until that fade finishes.
animation.ignition.delay and animation.extinction.delay use animation update
counts, while transitions.*.duration uses milliseconds. For a vertical cascade,
the countdown is the row's directional index multiplied by delay; fractional
values are allowed and the countdown advances once per animation update.
transitions.*.delay is not applied by the LED runtime.
API
class Ledding {
// Properties
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
options: LeddingOptions;
isRunning: boolean;
// Methods
setup(): void; // Recalculate dimensions
pause(): void; // Pause animation
resume(): void; // Resume animation
setPattern(pattern: number[][]): void; // Change pattern
getFrameRate(): number; // Get configured FPS
getLedCount(): number; // Get current LED count
getLayoutMetrics(): LayoutMetrics; // Get bounds, margins and pixel ratio
destroy(): void; // Clean up resources
// Events
on(event: LeddingEventType, callback: Function): void;
off(event: LeddingEventType, callback: Function): void;
// Event types: 'beforeDraw', 'afterDraw', 'resize', 'destroy'
}Aligners
Control art positioning within the canvas:
import {
TopLeftAligner,
TopAligner,
TopRightAligner,
LeftAligner,
CenterAligner, // Default
RightAligner,
BottomLeftAligner,
BottomAligner,
BottomRightAligner
} from 'ledding';Built-in aligners use logical CSS pixels and the visible LED bounds. They do
not count a trailing gap after the last LED, so centered and edge-aligned
patterns have exact, symmetric margins. Custom aligners return the center of
the first LED as artStartPx and artStartPxY.
Resolved layout data is available at any time:
const { viewport, pattern, position, margins } = ledding.getLayoutMetrics();Renderers
Control LED shape:
import { CircleRenderer, SquareRenderer } from 'ledding';
// CircleRenderer: Optimized with Path2D caching
// SquareRenderer: Simple rectangle fillDirections
import { Directions } from 'ledding';
Directions.TO_LEFT
Directions.TO_RIGHT
Directions.TO_TOP
Directions.TO_BOTTOM
Directions.TO_TOP_LEFT
Directions.TO_TOP_RIGHT
Directions.TO_BOTTOM_LEFT
Directions.TO_BOTTOM_RIGHTPatterns
import { Pattern } from 'ledding';
Pattern.CASCADE // Sequential activation
Pattern.INTERLACED // Stripe-based activation
Pattern.WAVE // Multiple cascade waves
Pattern.RANDOM // Random activation orderPerformance Optimizations
- Color caching: RGB strings are cached to avoid creating 20K+ strings/second
- Sparse grid mode: Only active LEDs are tracked and rendered
- Pre-bound functions: No
.bind()in animation loop - Proper cleanup: No memory leaks on destroy
- Frame limiting: Configurable FPS cap
- Visibility API: Pauses when tab is hidden
- Typed arrays: Uint8ClampedArray for color operations
- Bounded scroll offsets: Prevents long-running floating-point drift
- Draw culling: Skips LEDs outside the logical viewport
- Reusable sparse-grid cache: Avoids allocating a key set every frame
Bundle Sizes
- ESM: 68.9KB (29.1KB minified, 8.7KB gzipped)
- UMD: 78.3KB (29.4KB minified, 8.8KB gzipped)
- CommonJS: 70.2KB
- TypeScript declarations: 18.6KB
Tree Shaking
Import only what you need:
// Full bundle
import { Ledding, CircleRenderer, CenterAligner } from 'ledding';
// Minimal - only core
import { Ledding } from 'ledding';
import { CircleRenderer } from 'ledding/renderers';
import { CenterAligner } from 'ledding/aligners';Browser Support
- Chrome 69+
- Firefox 62+
- Safari 12+
- Edge 79+
Requires:
- Canvas 2D API
- ES6 Modules
- Path2D
- requestAnimationFrame
- Map/Set
Development
# Install dependencies
npm install
# Build
npm run build
# Type check
npm run typecheck
# Watch mode
npm run devLicense
MIT
