GlyphHotspot
<GlyphHotspot> renders a real, absolutely-positioned <div> over the ASCII strip,
tracking a 3D anchor through every baked frame. The <div> is pointer-events: auto
inside an otherwise transparent hit layer — events fire on it like any normal DOM
element.
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Stable identifier for this hotspot |
at | Vec3 | string | required | World-space anchor. Either [x, y, z] or a string like "vertex:42" |
size | [number, number] | [1, 1] | Hitbox size in character cells |
onClick | MouseEventHandler | — | Click handler |
aria-label | string | — | Accessibility label |
className | string | — | CSS class |
children | ReactNode | — | Rendered inside the <div> (use for tooltips/badges) |
Tracking math
Section titled “Tracking math”For an animated scene, the hotspot is projected once per frame during the bake.
The resulting positions are emitted as a @keyframes rule with 0%, 1.667%,
3.333%, …, 100% waypoints — matching the strip’s steps(60, end) exactly.
When the user drags, the strip’s animation pauses and the hotspot’s transform is
live-updated per pointermove. On release, both animations re-start from the new
camera state.
Full examples
Section titled “Full examples”import { GlyphCamera, GlyphScene, GlyphMesh, GlyphOrbitControls, GlyphHotspot,} from "@glyphcss/react";import { cubePolygons } from "@glyphcss/core";
const cube = cubePolygons({ center: [0, 0, 0], size: 1, color: "#4488ff" });
export function HotspotDemo() { return ( <GlyphCamera rotX={25}> <GlyphScene mode="solid" cols={100} rows={30}> <GlyphOrbitControls /> <GlyphMesh polygons={cube}> {/* Hotspot on the top face */} <GlyphHotspot id="top" at={[0, 0.5, 0]} size={[5, 3]} aria-label="Top face" onClick={() => alert("top face clicked")} > <span className="badge">Top</span> </GlyphHotspot> {/* Hotspot on the right face */} <GlyphHotspot id="right" at={[0.5, 0, 0]} size={[5, 3]} aria-label="Right face" onClick={() => alert("right face clicked")} > <span className="badge">Right</span> </GlyphHotspot> </GlyphMesh> </GlyphScene> </GlyphCamera> );}<template> <GlyphCamera :rot-x="25"> <GlyphScene mode="solid" :cols="100" :rows="30"> <GlyphOrbitControls /> <GlyphMesh :polygons="cube"> <GlyphHotspot id="top" :at="[0, 0.5, 0]" :size="[5, 3]" aria-label="Top face" @click="onTopClick" /> <GlyphHotspot id="right" :at="[0.5, 0, 0]" :size="[5, 3]" aria-label="Right face" @click="onRightClick" /> </GlyphMesh> </GlyphScene> </GlyphCamera></template>
<script setup lang="ts">import { GlyphCamera, GlyphScene, GlyphMesh, GlyphOrbitControls, GlyphHotspot,} from "@glyphcss/vue";import { cubePolygons } from "@glyphcss/core";
const cube = cubePolygons({ center: [0, 0, 0], size: 1, color: "#4488ff" });function onTopClick() { alert("top face clicked"); }function onRightClick() { alert("right face clicked"); }</script>import { createGlyphScene } from "glyphcss";import { cubePolygons } from "@glyphcss/core";
const host = document.querySelector<HTMLElement>("#scene")!;const scene = createGlyphScene(host, { mode: "solid", cols: 100, rows: 30 });
scene.add(cubePolygons({ center: [0, 0, 0], size: 1, color: "#4488ff" }));
const topHotspot = scene.addHotspot( { id: "top", at: [0, 0.5, 0], size: [5, 3] }, () => alert("top face clicked"),);
const rightHotspot = scene.addHotspot( { id: "right", at: [0.5, 0, 0], size: [5, 3] }, () => alert("right face clicked"),);
// Remove a hotspot when no longer needed:// topHotspot.remove();Visibility
Section titled “Visibility”Hotspots behind the camera get opacity: 0 automatically. Add a CSS transition to
fade them gracefully:
.glyph-hotspot { transition: opacity 200ms ease;}The fade is free — the renderer writes opacity: 0 into the keyframe at any frame
where the anchor is behind the camera.
Anchors
Section titled “Anchors”at accepts either an explicit Vec3 or a string anchor:
| Anchor | Resolves to |
|---|---|
[x, y, z] | World-space coordinate |
"vertex:42" | Vertex index 42 of the parent mesh |
"face:roof" | Named face (requires mesh authoring) |
"centroid" | Mesh centroid |