Mirage
The public facade. It owns an Engine and, optionally, a SandwichRenderer.
import { Mirage } from "mirage-engine";Constructor
new Mirage(target: HTMLElement, config: MirageConfig)| Parameter | Type | Description |
|---|---|---|
target | HTMLElement | The subtree root to mirror. Must be in the document with a non-zero box. |
config | MirageConfig | Options. See Configuration. |
Throws [Mirage] Target element is required. when target is falsy, and
[Mirage] Cannot find a container (parent or option). when no mount container
can be resolved.
config is required. Pass an empty object ({}) if you want every
default — new Mirage(el) throws because the constructor reads
config.sandwich.
The constructor performs all synchronous setup: it injects the global
stylesheet, resolves the mount container, and builds the registry, renderer and
syncer. Nothing renders until you call start().
Methods
start()
start(): Promise<void>Boots the WebAssembly module (first call only), starts the DOM tracker and begins the render loop. If sandwich layering is enabled, initializes it too.
Async — always await it before touching meshes or uniforms.
await mirage.start();Calling start() after stop() resumes the loop. Tracker.start() is
idempotent, so a redundant call is harmless.
stop()
stop(): voidDisconnects the MutationObserver, removes the resize/transition listeners,
clears pending timers and ends the requestAnimationFrame loop. The scene,
meshes and canvas stay in memory, so start() picks up where it left off.
Use this when a tab is hidden or a route is inactive.
destroy()
destroy(): voidStops the loop, disposes the WebGLRenderer, removes the canvas from the DOM
and disposes tracked textures.
After destroy() the instance is spent — create a new Mirage rather than
calling start() again.
getCanvas()
getCanvas(): HTMLCanvasElementReturns the canvas Mirage created and mounted. Useful for applying your own CSS
(filter, mix-blend-mode, mask-image) or reading pixels.
mirage.getCanvas().style.mixBlendMode = "screen";getTracker()
getTracker(): TrackerReturns the underlying Tracker so you
can subscribe to lifecycle hooks.
const tracker = mirage.getTracker();
tracker.onRender.add(() => {
// runs every frame, after Mirage has synced meshes
});
tracker.onLayoutChange.add((mask, deletions) => {
console.log("layout changed", mask, deletions.size);
});Available hook sets: onBeforeRender, onLayoutChange, onScrollChange,
onStyleChange, onRender. Each is a plain Set, so remove a listener with
tracker.onRender.delete(fn).
updateUniforms()
updateUniforms(element: HTMLElement, uniforms: Record<string, any>): voidWrites uniform values straight into the material of the mesh that mirrors
element, plus all of its child meshes and its native mesh if one exists. This
is the fast path — it skips extraction and reconciliation entirely.
const card = document.querySelector("#card") as HTMLElement;
mirage.updateUniforms(card, {
uTime: performance.now() / 1000,
opacity: 0.5,
backgroundColor: "rgb(255, 0, 0)",
});Recognized built-in keys: width, height, borderRadius, borderWidth,
backgroundColor, borderColor, opacity, bgOpacity, borderOpacity,
texture, backgroundImage, boxShadow. Any other key is forwarded to a
custom uniform of the same name — declare it via
data-mirage-shader first.
Silently does nothing if the element has no mesh yet.
test()
test(): voidInternal debugging aid. Binds arrow keys to move the mesh mirroring #box2.
Not part of the supported API; do not ship it.
Complete lifecycle
import { Mirage } from "mirage-engine";
const target = document.querySelector("#app") as HTMLElement;
const mirage = new Mirage(target, { mode: "overlay", quality: "high" });
await mirage.start();
const tracker = mirage.getTracker();
const onFrame = () => {
mirage.updateUniforms(target, { uTime: performance.now() / 1000 });
};
tracker.onRender.add(onFrame);
// later
tracker.onRender.delete(onFrame);
mirage.destroy();