Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Class OverlayManager

    Manages the screen-space overlay layer (text, image, and HTML overlays) drawn over the WebGL canvas but beneath the UI controls.

    Keeps a keyed set of overlay divs and their OverlayConfigs, applies anchor/blend/font styling, and updates visibility as scene dimensions change (subscribing to sceneDimsManager) so overlays appear only on the slice positions they belong to, with CSS-transition fades.

    Index
    overlayElements: Map<string, HTMLDivElement> = ...
    configs: Map<string, OverlayConfig> = ...
    objectUrls: Set<string> = ...
    videoElements: Map<string, HTMLVideoElement> = ...

    Video overlays by name, so visibility can start/stop playback.

    matteCompositors: Map<string, VideoMatteCompositor> = ...

    Stacked-alpha-matte compositors, for the VISIBLE clips only.

    A compositor owns a WebGL context, and a browser caps how many may be live at once — Chrome then evicts the OLDEST, which is the scene's own renderer. A tour with more stacked clips than that cap (nineteen turntables, against a cap around sixteen) therefore lost the renderer partway through when each clip kept a context for the session. So a compositor is built when its clip is shown and destroyed, canvas and all, once its hide transition completes: see showMatte and scheduleMatteHide. The canvas cannot be kept and merely emptied — WEBGL_lose_context is not reversible by asking that same canvas for a context again, which silently leaves a dead one behind.

    matteSpecs: Map<
        string,
        { el: HTMLDivElement; video: HTMLVideoElement; config: OverlayConfig },
    > = ...

    What showMatte needs to rebuild a compositor for a clip.

    matteHideTimers: Map<string, Timeout> = ...

    Fade-out timers that hand matte contexts back after the canvas disappears.

    matteAbandoned: Set<string> = ...

    Clips whose compositor could not read them: show the raw clip, never retry.

    baseUrl: string = ''
    boundDimChangeHandler: () => void
    globallyHidden: boolean = false

    Whether overlays are globally hidden by the user toggle (U key)

    transitGate: (() => boolean) | null = null

    Arrival gate for story flights (Waypoint.reveal = "on_arrival"): while it returns true, a dimension-bound overlay that is not already showing stays hidden. null = no gate.

    shown: Set<string> = ...

    Non-hover overlays currently shown.

    settled: Set<string> = ...

    shown as it was BEFORE the current dimension position was reached — what the arrival gate keeps as is. Snapshotted when updateVisibility() first sees a new position, so a second pass at the same position (the app re-runs it once the driver has decided the gate) still knows which overlays were already on screen and which only just appeared.

    settledAt: string | null = null
    hoverOverlays: Map<string, HoverOverlayEntry> = ...

    Hover overlays that update from GPU picking results.

    _lastHoverLabel: string | null = null

    Cache last hover result to skip redundant DOM updates.

    _lastHoverKey: string | null = null
    _lastHoverImageUrl: string | null = null
    _lastHoverIndex: number = -1
    _lastHoverNode: string | null = null
    • Load overlays from parsed configs and render them.

      Parameters

      • overlayConfigs: OverlayConfig[]

        Array of overlay configurations from zarr

      • baseUrl: string

        Base URL of the zarr store (for directory image fetching)

      • OptionalreadFile: OverlayFileReader

        Reader for opaque files held inside the active store

      Returns Promise<void>

    • True if there is at least one hover overlay that could currently display a pick result. PickingSystem consults this via its shouldPick predicate to avoid paying picking cost when no tooltip would be displayed.

      Returns boolean

    • Install (or clear, with null) the arrival gate. While gate() is true — the waypoint driver is flying to a waypoint authored reveal: "on_arrival" — updateVisibility() keeps a dimension-bound overlay that would NEWLY appear hidden, hides one that stops matching at once (leaving is instant), and leaves overlays without a visible_range alone. The app re-runs updateVisibility() when the flight resolves so the held overlays fade in together, on arrival.

      Parameters

      • gate: (() => boolean) | null

      Returns void

    • Whether config shows now: the dimension rule, the global toggle, and the arrival gate (which only ever withholds a dimension-bound overlay that is not already on screen). Keeps shown in step with the answer.

      Parameters

      Returns boolean

    • Re-snapshot settled when the dimension position changed since the last pass. The overlay manager and the waypoint driver both listen to the dims manager in unspecified order: if this pass runs first it may show the new story's captions before the driver closes the gate; the app then re-runs it at the SAME position, and the snapshot lets that second pass withhold exactly the overlays the first one had just revealed.

      Returns void

    • Update hover overlay content from a GPU picking result.

      Substitutes template variables ({hover_label}, {hover_key}, {hover_image_label}, {hover_node}, {hover_index}) in all hover overlays. Fades out if result is null or has no content.

      Parameters

      • result:
            | {
                label?: string
                | null;
                key?: string | null;
                imageUrl?: string | null;
                nodeName: string;
                elementIndex: number;
            }
            | null

        Pick result with label text, or null to clear

      Returns void

    • Return all currently visible overlay elements and their configs. Used by the recording panel to composite overlays onto the capture canvas.

      Returns { el: HTMLDivElement; config: OverlayConfig }[]

    • Create video overlay content: a muted, looping <video> (the only autoplay a browser permits without a gesture), served from a blob URL for zipped stores or by URL otherwise. Playback is tied to visibility in updateVisibility so a hidden turntable does not keep decoding.

      Parameters

      • el: HTMLDivElement
      • config: OverlayConfig
      • OptionalarchivedVideo: Uint8Array<ArrayBufferLike>
      • OptionalarchivedPoster: Uint8Array<ArrayBufferLike>

      Returns void

    • Whether the store's base URL is on another origin than the page (blob URLs are not).

      Returns boolean

    • Build and attach the compositor canvas for a stacked-matte clip that is becoming visible, and return it started. The <video> stays in the DOM, decoding, but visually hidden — it is the compositor's frame source — and the poster sits behind the canvas until the first frame is drawn.

      Idempotent: an already-live compositor is returned as it is. A clip that has been abandoned (a tainted cross-origin video, or no WebGL at all) is never rebuilt, so a failing clip is reported once rather than at every story step.

      Parameters

      • name: string

      Returns VideoMatteCompositor | undefined

    • Destroy the compositor of a clip that has gone off screen, canvas and all, so its WebGL context is handed back (see the note on matteCompositors). The canvas is DISCARDED rather than kept and reused: a context lost through WEBGL_lose_context cannot be re-acquired from the same canvas, and asking it for one again yields a dead context that silently draws nothing.

      Parameters

      • name: string

      Returns void

    • Keep the last composited frame alive until a CSS fade has completed.

      Parameters

      • name: string
      • delayMs: number

      Returns void

    • The compositor could not read the video (a tainted cross-origin clip): drop the canvas and show the raw clip — colour over matte, visible rather than a blank square — and say why, once.

      Parameters

      • name: string
      • video: HTMLVideoElement
      • error: unknown

      Returns void

    • Parameters

      • video: HTMLVideoElement
      • config: OverlayConfig
      • OptionalarchivedVideo: Uint8Array<ArrayBufferLike>
      • OptionalarchivedPoster: Uint8Array<ArrayBufferLike>

      Returns boolean

    • Start or stop a video overlay with its visibility (muted play needs no gesture).

      Parameters

      • name: string
      • visible: boolean

      Returns void

    • Create image overlay content.

      Parameters

      • el: HTMLDivElement
      • config: OverlayConfig
      • OptionalarchivedImage: Uint8Array<ArrayBufferLike>

      Returns void

    • Client-side HTML sanitization against DOM tag and attribute allowlists.

      Pass 1 is an attribute ALLOWLIST (see ALLOWED_ATTRS): every attribute not in the set is removed — this is what drops on* handlers, DOM-clobbering id/name, ping/srcset/download, data-*, etc. The allowlisted value-bearing attributes then pass a per-attribute guard (see below): href/src block javascript:/vbscript:/data: schemes, style blocks javascript:/vbscript:/expression( plus the CSS escape and comment syntax (\, /*) that could smuggle those tokens past a substring check, rel drops an opener token, and target is restricted to _blank/_self — the last two neutralize reverse tabnabbing. Pass 2 is a tag allowlist (see ALLOWED_TAGS).

      A disallowed tag is unwrapped, not dropped — its children are lifted into its parent — so every element has to be scrubbed whether or not its own tag survives. Skipping the descendants of a disallowed tag hoists them into the output verbatim; that was issue #720.

      Invariant: pass 1 scrubs attributes on every element unconditionally, and pass 2 only moves existing nodes and drops the elements it unwraps — it never creates, clones or re-parses one, so nothing can reach the output unscrubbed. An unwrapped element is removed only after its children have been lifted, so nothing still awaiting pass 2 is ever detached: every element left in the snapshot is still connected, and still scrubbed, when pass 2 reaches it. Pass 2 runs outermost-first so each node moves exactly once; bottom-up would re-lift the same payload once per enclosing wrapper.

      Note: a nested <template> keeps its payload in a separate .content fragment that querySelectorAll never sees. It is discarded because template is not allowlisted — allowlisting it would ship that subtree unsanitized.

      Parameters

      • html: string

      Returns string