@glowbox/vue
v1.12.0
Published
glowbox components for Vue 3 — <LedGrid> (3D WebGL LED grid), <NixieTube>, <SevenSegment>, <FlipDots>, <SplitFlap>, <NeonSign>, <VfdPanel> and <LcdModule>, over the sibling display cores.
Maintainers
Readme
@glowbox/vue
glowbox components for Vue 3 — one per display core: <LedGrid> (the 3D WebGL LED
grid), <NixieTube>, <SevenSegment>, <FlipDots>, <SplitFlap>, <NeonSign>,
<VfdPanel> and <LcdModule>, each a thin live-updating wrapper over its sibling
core package.
⚡ Open in StackBlitz —
a running <LedGrid> + <NixieTube> playground in your browser, no install.
yarn add @glowbox/vue
# peer: vue ^3 (the eight display cores come along as dependencies)<LedGrid>
<script setup lang="ts">
import { LedGrid, type LedDisplay } from '@glowbox/vue';
const draw = (d: LedDisplay, dt: number) => {
d.clear();
d.sphere([4, 4, 4], 3, '#00aaff');
};
</script>
<template>
<div style="width: 480px; height: 480px">
<LedGrid
:size="[8, 8, 8]"
:draw="draw"
:led="{ glow: 3, offColor: '#0a0a12' }"
:camera="{ autoOrbit: true, projection: 'perspective' }"
:color="{ background: '#000', gain: 1.1 }"
:interaction="{ zoom: true }"
/>
</div>
</template>| prop | type | notes |
| ------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| size | [number, number, number] | grid dims [nx, ny, nz] (changing it resizes in place — no remount) |
| draw | (d: LedDisplay, dt: number)=>void | called every frame (dt in seconds); write voxels here |
| led | LedOptions | style shape stagger rgb rgbLayout vivid outline outlineColor size offSize glow offColor |
| color | ColorOptions | background gain tint |
| camera | CameraOptions | yaw pitch distance fov projection autoOrbit orbitSpeed pitchLimits (autoOrbit defaults off under prefers-reduced-motion) |
| interaction | InteractionOptions | drag dragSpeed zoom zoomLimits |
| quality | QualityOptions | pixelRatio antialias paused fps (frame-rate cap) alpha (transparent canvas; fixed at creation) |
| label | string | accessible name for the canvas (aria-label; default 'LED grid') |
| oncreate | (d: LedDisplay \| null)=>void | the display on create, null on teardown — bind as :oncreate="fn" (@create would look for onCreate) |
The component expose()s the imperative LedDisplay handle as display on the component
ref (snapshot(), stats, setCamera, …) — or take it from oncreate:
<script setup lang="ts">
import { ref } from 'vue';
const grid = ref();
// grid.value?.display?.snapshot()
</script>
<template><LedGrid ref="grid" :size="[8, 8, 8]" /></template>The grouped props mirror @glowbox/led-grid's options 1:1 and update live. draw is
bound as one onFrame subscription — onFrame callbacks stack, so subscribing more
via the handle layers on top of draw rather than replacing it. See
@glowbox/led-grid for defaults, the voxel API, and colour semantics.
<NixieTube>
<script setup lang="ts">
import { NixieTube } from '@glowbox/vue';
</script>
<template>
<div style="width: 80px; height: 150px">
<NixieTube value="7" tube-style="classic" color="#ff6a12" />
</div>
</template>| prop | type | notes |
| ------------ | ------------------------------- | ---------------------------------------------------------- |
| value | string \| number \| null | the lit symbol: 0–9, :, -, or null/'' for dark |
| tubeStyle | 'classic' \| 'slim' \| 'tall' | physical tube style (maps to the core style option) |
| color | Color | glow colour (default warm nixie orange) |
| glow | number | glow strength 0..1 |
| background | Color | tube glass colour |
| mesh | boolean | draw the honeycomb anode mesh (default true) |
| ghost | boolean | show the unlit cathode stack (default true) |
| pixelRatio | number | cap on devicePixelRatio |
| label | string | accessible name (aria-label); defaults to the lit symbol |
| oncreate | (t: NixieTube \| null)=>void | the tube on create, null on teardown (bind :oncreate) |
Props update live; the tube handle is expose()d as tube (setValue, setOptions,
resize, snapshot) — or take it from oncreate. The core's bare mode (transparent canvas for 3D compositing) is
core-only — use createNixieTube directly for that. See @glowbox/nixie for defaults +
the size-adaptive rendering.
<SevenSegment>
<script setup lang="ts">
import { SevenSegment } from '@glowbox/vue';
</script>
<template>
<div style="width: 76px; height: 130px">
<SevenSegment value="7" display-style="vfd" :age="0.4" />
</div>
</template>A seven-segment digit over @glowbox/seven-segment —
per-segment cross-fades, ageing, LED/VFD materials. value takes 0–9, -, hex
A b C d E F, : (the clock separator — give it a narrow slot), or null for dark;
displayStyle ('led' | 'vfd'), dp, color, background, glow, ghost,
age (0..1 — dimming, then flicker, then a dead segment), transition (per-segment
fade ms), pixelRatio, label mirror the core options and update live; the handle
is expose()d as display, or comes through :oncreate. See
@glowbox/seven-segment for defaults, the ageing arc, and the geometry export.
<FlipDots>
<script setup lang="ts">
import { FlipDots } from '@glowbox/vue';
</script>
<template>
<div style="width: 560px; height: 280px">
<FlipDots :cols="28" :rows="14" :frame="(x, y) => (x + y) % 2" sound />
</div>
</template>An electromechanical flip-dot board over @glowbox/flip-dot —
physical disc flips, scan-wave stagger, an optional solenoid click. frame takes
row-major 0/1 bits (ditherFrame output fits) or an (x, y) => on function — only dots
that actually change flip; cols, rows, shape ('disc' | 'square'), onColor,
offColor, board, gap, shaded, flipMs, axis, stagger ('scan' |
'random' | 'none'), scanMs, sound (true = 0.5, or a 0..1 volume),
pixelRatio, label mirror the core options and update live; the handle is
expose()d as board, or comes through :oncreate — the imperative FlipDotBoard
(set, setFrame, dotAt/dotRect hit-testing). See @glowbox/flip-dot for
defaults and ditherFrame.
<SplitFlap>
<script setup lang="ts">
import { SplitFlap } from '@glowbox/vue';
</script>
<template>
<div style="width: 640px; height: 80px">
<SplitFlap :cols="12" text="DEPARTURES" sound />
</div>
</template>A Solari split-flap board over @glowbox/split-flap —
forward-only drum wraps, perspective card falls, a card-slap. text takes a string
('\n' splits rows) or one string per row; cols, rows, charset (plus per-field
drums and per-flap palette — see chromaDrum), card, ink, board, gap,
font, shaded, flipMs, sound, pixelRatio, label mirror the core options and
update live; the handle is expose()d as board, or comes through :oncreate —
the imperative SplitFlapBoard (setLine/setChar, cellAt/cellRect hit-testing).
See @glowbox/split-flap for the drum model and the colour drums.
<NeonSign>
<script setup lang="ts">
import { NeonSign } from '@glowbox/vue';
</script>
<template>
<div style="width: 640px; height: 240px">
<NeonSign text="OPEN" gas="neon" program="flash" sound />
</div>
</template>A glass-tube neon sign over @glowbox/neon — single-stroke
tube letterforms, electrode strike sequences, visible unlit glass, transformer hum. A
text change re-glasses and strikes on; font ('script' | 'sans' | a custom
NeonFont), art (sign artwork from SVG path data), color, gas, wall, polarity
('absorb' inks a pale wall — the light-theme neon), on, lineOn, glow, glass,
electrode, age, flicker, tired, program ('steady' | 'flash' | 'chase'
| 'reveal'), speed, tubes, align, lineSpacing, letterSpacing, tilt,
padding, strikeMs, sound, mains, pixelRatio, label mirror the core options
and update live; the handle is expose()d as sign, or comes through :oncreate —
the imperative NeonSign (power, jolt, sectionAt/sectionRect hit-testing). See
@glowbox/neon for the gases, the art pieces, and the wear arc.
<VfdPanel>
<script setup lang="ts">
import { VfdPanel } from '@glowbox/vue';
import type { VfdElement } from '@glowbox/vue';
const layout: VfdElement[] = [
{ kind: 'digits', name: 'track', x: 8, y: 8, w: 120, h: 48, chars: 4 },
{ kind: 'legend', name: 'play', x: 140, y: 8, w: 40, h: 16, text: 'PLAY' }
];
</script>
<template>
<div style="width: 640px; height: 128px">
<VfdPanel :frame="[320, 64]" :layout="layout" :values="{ track: 'A-12', play: true }" />
</div>
</template>A vacuum-fluorescent panel over @glowbox/vfd — declare the
hardware once (frame + layout: digits, legends, bars, icons, scales, dot grids,
rules) and drive content through values, by element name: a string/number drives a
digits field or a scale cursor, a boolean a legend/icon, a number[] a bars
element (only changed entries are pushed — the type is exported as VfdValue). The
envelope props (phosphor, filter, zones, brightness, persistence, filament,
grid, age, glow, bezel, glass, on, selfTest, pixelRatio, label) update
live and never re-compile; a layout change goes through setLayout, the one
expensive call. The handle is expose()d as panel, or comes through :oncreate —
animated content (setDots, a spectrum's setBars) wants it. See @glowbox/vfd for
the element kinds and the envelope physics.
<LcdModule>
<script setup lang="ts">
import { LcdModule } from '@glowbox/vue';
</script>
<template>
<div style="width: 480px; height: 150px">
<LcdModule :text="'HELLO\nWORLD'" cursor="block" />
</div>
</template>A character LCD module (HD44780-class) over @glowbox/lcd —
the family's first reflective display: liquid-crystal response smear, the contrast pot,
crosstalk streaks, the boot row of solid blocks. text takes a string ('\n' splits
rows) or one string per row — the shutters chase it at crystal speed; cols, rows,
panel ('green' | 'blue' | 'white'), backlight, contrast, response,
ghost, cursor ('none' | 'line' | 'block'), age, on, boot, bezel,
pixelRatio, label mirror the core options and update live; the handle is
expose()d as lcd, or comes through :oncreate — the imperative LcdModule
(setGlyph for CGRAM custom characters, setCursor, power, cellAt/cellRect
hit-testing). See @glowbox/lcd for the panels and the crystal physics.
Using with @glowbox/crt
There is deliberately no <Crt> component: @glowbox/crt's
element mode already composites every descendant canvas, so add it as its own dependency
and wrap the stage element imperatively:
<script setup lang="ts">
import { createCrtScreen, type CrtScreen } from '@glowbox/crt';
import { NixieTube } from '@glowbox/vue';
import { onMounted, onUnmounted, ref } from 'vue';
const stage = ref<HTMLDivElement | null>(null);
let crt: CrtScreen | null = null;
onMounted(() => (crt = stage.value ? createCrtScreen(stage.value) : null));
onUnmounted(() => crt?.dispose());
</script>
<template>
<div ref="stage" style="width: 80px; height: 150px">
<NixieTube value="7" />
</div>
</template>Sibling packages with the same components: @glowbox/svelte and @glowbox/react; content helpers in @glowbox/extras. Each component fills its parent; give the parent a size. Live demos: https://eetu.github.io/glowbox/.
