Classes
The composition tree. what owns what, and what passes through.
Every class in pixi-reels lives on one of three layers: theouter shell the user builds, the scene graphPixiJS renders, or the internals that exist for the duration of a spin. Ownership reads top-to-bottom: the parent always destroys its children on destroy().
What each class does. in plain English
If a name feels opaque, read its one-liner here first. The SVG after this section shows how the names are wired together.
ReelSetBuilderbuilderA fluent, chainable configurator. You call .reels(5).visibleCells(3)... and end with .build(). It exists so every required piece of setup is forced into one place. Forget .ticker(app.ticker) and .build() throws before your game boots.
ReelSetscene graph rootThe thing you app.stage.addChild(reelSet). A PixiJS Container that owns every reel, the spin controller, the speed manager, and the win spotlight. All the public methods you'll reach for live here: spin(), setResult(), skipSpin(), destroy().
Reelone columnOne vertical column of symbols. A Reel owns the symbols currently on screen, their vertical position, and the "what do I land on" queue. You rarely touch a Reel directly. you drive the whole ReelSet and let it fan out.
ReelViewportclip + layerThe clipped window you see through. Holds the mask so symbols scrolling above or below the visible area are hidden, plus a "promoted" layer where winning symbols get temporarily lifted above the mask so their celebration animation isn't clipped.
ReelMotionvertical scrollThe physics of one reel. Adds a Y delta every tick and wraps symbols that fall off the bottom back to the top. When a symbol wraps, it fires a callback that tells the reel "time to swap identity". that's how a spinning reel eventually shows the target grid.
StopSequencerlanding queueA tiny queue that holds the symbols a reel must land on, in order. On every wrap, the sequencer hands out the next target. When the queue is empty the reel stops.
SpinControllerconductorThe conductor of a spin. Hands every reel its next phase (Start → Spin → optional Anticipation → Stop), coordinates with setResult(), fires the spin:* events, and resolves the promise you awaited on.
ReelPhasestateAbstract base for a single stage of a reel's spin. Each stage gets onEnter, update (per-tick), and onSkip. Stock phases: StartPhase (wind-up), SpinPhase (steady-state blur), AnticipationPhase (slow-down for tension), StopPhase (decelerate, snap, bounce).
SpinningModestrategyThe top-level "what does a spin even mean" strategy. StandardMode runs every reel through the phase machine. CascadeMode replaces whole columns atomically for tumble mechanics. ImmediateMode snaps straight to the result. useful in tests.
SpeedManagertempoNamed bundles of timings (spin speed, stop delay, bounce distance, easings). Ship with normal, turbo, superTurbo presets. Switch at runtime with reelSet.setSpeed('turbo'); add your own with addProfile().
ReelSymbolabstract cellOne visible cell on a reel. Abstract base class. subclass it to draw whatever you like. Built-in subclasses: SpriteSymbol (plain texture), AnimatedSpriteSymbol (sprite sheet), SpineSymbol (Spine skeleton, optional peer). Implement onActivate, onDeactivate, playWin, stopAnimation, resize for a new kind.
SymbolRegistry / SymbolFactorypoolSymbolRegistry records "symbol id X is rendered by class Y with options Z". SymbolFactory is the cache on top. it pools instances so scrolling a 5x3 reel for five seconds allocates zero new symbol objects.
FrameBuilder + middlewarepipelineA middleware pipeline that decides which symbol identity fills each new buffer slot when a reel needs another row. Random fill is priority 0, target-frame placement is priority 10. Register your own middleware to implement rules like "no three-in-a-row" or "inject a mystery symbol every 7th spin".
SymbolSpotlightwinsThe win-animation primitive. show(positions) dims the losers and runs playWin() on the winners, promoted above the mask. cycle(lines) iterates through multiple lines with a configurable cadence and emits spotlight:start / spotlight:end events.
EventEmitterpub / subTyped pub/sub. Event names are colon-namespaced (spin:start, spin:reelLanded, speed:changed). Every exit path from a spin fires an event. wire audio and HUD to events, not to method calls, so skipSpin() and destroy() stay correct without your HUD knowing about them.
TickerRefsafe tickerA thin wrapper around PIXI.Ticker that tracks every callback it adds and removes them all on destroy(). Nobody in this codebase calls ticker.add() directly. that's how we keep teardown correct.
DisposableinterfaceThe cleanup contract. Anything that allocates implements it. reelSet.destroy() cascades through the whole tree. you never have to chase individual pieces yourself.
FakeTicker + HeadlessSymboltestsThe headless harness. A manual ticker you step frame by frame, plus a symbol class that draws nothing. With these you can run an entire spin lifecycle in Node, assert on the final grid, and never load PixiJS's renderer.
flowchart TB
Builder["ReelSetBuilder<br/><i>.reels() · .symbols() · .build()</i>"]
ReelSet["ReelSet<br/><i>extends PIXI.Container · implements Disposable</i>"]
Builder -.->|creates| ReelSet
ReelSet --> SpinController["SpinController<br/><i>phase orchestrator</i>"]
ReelSet --> SpeedManager["SpeedManager<br/><i>named profiles</i>"]
ReelSet --> SymbolSpotlight["SymbolSpotlight<br/><i>win cycle</i>"]
ReelSet --> SymbolFactory["SymbolFactory<br/><i>pool owner</i>"]
ReelSet --> FrameBuilder["FrameBuilder<br/><i>middleware pipeline</i>"]
subgraph ReelsGroup["Reels[]"]
direction LR
Reel0["Reel #0"]
Reel1["Reel #1"]
Reel2["Reel #2"]
Reel3["Reel #3"]
Reel4["Reel #4"]
end
ReelSet --> Reel0
subgraph PerReel["Per reel (one Reel contains)"]
direction TB
Container["container: PIXI.Container<br/><i>child of ReelViewport.maskedContainer</i>"]
Symbols["symbols: ReelSymbol[]<br/><i>buffer + visible + buffer · pooled by SymbolFactory</i>"]
Events["events: EventEmitter<ReelEvents><br/><i>phase:enter · phase:exit · landed · symbol:created</i>"]
Motion["motion: ReelMotion<br/><i>displace · wrap · snapToGrid</i>"]
StopSequencer["stopSequencer: StopSequencer<br/><i>target frame queue · next() pops each wrap</i>"]
SpinningMode["spinningMode: SpinningMode<br/><i>Standard · Cascade · Immediate · computeDeltaY()</i>"]
end
ReelsGroup -.->|one reel contains| PerReel
Reading the ownership chain
ReelSet.destroy() is the single source of truth for teardown. Internally it:
- Destroys the
SymbolSpotlight(releases its tween handles). - Destroys the
SpinController, which removes theTickerRefcallback. - For each
Reel: releases every pooled symbol back to theSymbolFactory, removes event listeners, destroys the PixiJS container tree. - Destroys the
SymbolFactory, which disposes every class instance in its pool. - Destroys the
ReelViewport(masked container). - Emits
destroyed, removes every listener, callssuper.destroy({ children: true }).
If you extend pixi-reels with a new subsystem that holds resources, it must implement the Disposable interface and be destroyed from this chain. there's no hidden GC to catch you.
Disposable everywhere
ReelSetowns: viewport · reels · factory · subsystemsReelowns: motion · stopSequencer · events · containerSpinControllerowns: TickerRef · active phasesSymbolSpotlightowns: gsap timelines · overlay containersSymbolFactoryowns: ObjectPool<ReelSymbol>TickerRefowns: registered ticker callbacksReelSymbolowns: view container · subclass resourcesExtension points
The classes on the outer edges are where you plug in custom code:
- Extend
ReelSymbolfor a new rendering style (Spine, animated sprite, custom Graphics). - Extend
ReelPhase<TConfig>for a new spin phase, register it viabuilder.phases(f => f.register(...)). - Implement
SpinningModefor a different motion model (cascade, hover, immediate). - Implement
FrameMiddlewareto hook into per-frame symbol generation. random fill and target placement are themselves just middleware.