Overflowing symbol art runs into three different boundaries, in order of reach:
- The tile next to it (same reel) — fixed by
symbolDatazIndex. - The reel to its right — reels are separate containers and rightmost reels draw on top, so no zIndex can cross that line.
- The viewport mask — everything inside a reel is clipped to the grid.
Each demo below solves one more of them.
1. Within a reel: symbolData zIndex
Loading recipe…
The scatter is registered with zIndex: 10; the mystery bush is
deliberately left at the default layer. Watch a few spins: the tile below
the bush paints over its leaves (within a layer, bottom rows draw in front),
while the jaw is never clipped by anything in its own reel.
builder.symbolData({ scatter: { zIndex: 10 } });
The engine multiplies zIndex by 100 and adds the row index, so elevated
symbols still stack correctly among themselves. The library’s full z-budget
(default cells ~0-10, elevated 100+, big-symbol anchors ~500, pin overlays
10000, spotlight above all) is documented on ReelSet.
Limits — read this twice: zIndex sorts within one reel’s container
only. Reels are separate display containers, so no zIndex value can ever
lift a left reel’s symbol above the reel on its right, and nothing inside
a reel escapes the viewport mask. Crossing reels or the mask takes
unmask (demo 2) or promotion (demo 3) — there is no third way.
2. Cross-reel and out-of-mask: unmask
Loading recipe…
unmask: true is an at-rest presentation. While the reel spins, the
symbol stays masked like everything else — nothing scrolls visibly outside
the grid. On land, every visible-row instance of that symbolId is lifted
into the viewport-wide unmaskedContainer: above every reel (this is
the only declarative way a left reel’s symbol beats its right neighbour)
and on top of the mask. The next spin start hands it back to its reel.
This demo runs on the same jagged 3-4-4-4-4-3 grid, with scatters forced
onto the short outer reels’ edge rows — the landed jaw pokes past the
stepped outline and over its neighbour, with zero recipe code:
builder.symbolData({ scatter: { zIndex: 10, unmask: true } });
Jagged layouts work too. The lifted view lives in the viewport-wide
container (at 0,0), so the engine bakes the reel’s offsetY into its Y —
and re-bakes it after every absolute motion snap
(Reel._syncUnmaskedViewOffsets). Because a view is only lifted while the
reel is at rest, the frequent mid-spin snaps never touch it, so the short
centre-shifted reels stay aligned. (Earlier versions threw on offset reels;
that restriction is gone.)
Notes:
- Buffer-row instances are never lifted — an off-screen scatter parked beyond the mask edge would read as a rendering bug.
- With a horizontal
symbolGap, the builder auto-selectsSharedRectMaskStrategyso masked neighbours aren’t clipped at the gap stripe next to an unmasked overlay.
3. Finer control: promote into the spotlight layer
Loading recipe…
unmask is per-symbolId and declarative — every instance of that symbol,
every spin. When you need per-instance control (lift only the scatters
that are part of a win, layer above pins, or promote something that isn’t a
reel symbol at all), lift the landed view yourself.
viewport.spotlightContainer is the engine’s top layer (above pins, above
the mask), and the symbol pool is explicitly built to tolerate this
re-parenting — it’s what the win spotlight does:
function promote(sym) {
const layer = reelSet.viewport.spotlightContainer;
const globalPos = sym.view.getGlobalPosition();
promoted.push({ view: sym.view, parent: sym.view.parent, x: sym.view.x, y: sym.view.y });
layer.addChild(sym.view);
sym.view.position.copyFrom(layer.toLocal(globalPos));
}
reelSet.events.on('spin:start', () => {
for (const p of promoted) {
p.parent.addChild(p.view); // hand it back before the strip moves
p.view.position.set(p.x, p.y);
}
promoted.length = 0;
});
Promote ~380ms after the reel’s spin:reelLanded so the stop bounce has
settled (guard the timer with a spin-generation counter in case the player
slams into a new spin). Reading the global position and translating through
toLocal means this needs no per-reel offset math — it works on any layout.
The scatters here land on the short edge reels; their jaws hang past the
grid’s stepped outline until the next spin reclaims them.