Skip to Content
HooksuseViewerEffects

useViewerEffects

Create high-frequency material, transform, and lighting effects without accessing Three.js objects or putting animation values in React state.

The hook runs one request-animation-frame scheduler, invalidates demand-rendered viewers automatically, and restores runtime changes when it unmounts.

Flicker example

import { useEffect } from "react"; import { ModelViewer, useViewerEffects, type ViewerEffectChannels, } from "@liveroom-tech/react-immersive"; const channels: ViewerEffectChannels = { materials: [ { targets: { group: "filaments" }, values: { emissiveIntensity: [0.02, 8] }, }, ], lights: [ { lights: ["light-left", "light-right"], intensity: [0, 180], }, ], }; function Bulbs() { const { addPointLight, flicker, handleViewerReady, overrideEffect, stopEffect, } = useViewerEffects(); useEffect(() => { const id = flicker(channels, { id: "bulbs", minLevel: 0.1, maxLevel: 1, blackoutChance: 0.1, synchronized: true, }); return () => stopEffect(id); }, [flicker, stopEffect]); return ( <> <ModelViewer modelUrl="/bulbs.glb" licenseKey={licenseKey} objectBindings={objectBindings} onViewerReady={(viewer) => { handleViewerReady(viewer); addPointLight({ id: "light-left", target: "filament-left", color: "#ff9a3c", intensity: 0, distance: 30, }); }} /> <button onClick={() => overrideEffect("bulbs", 0, 800)}> Blackout </button> </> ); }

Material and transform targets resolve by binding map key, binding.id, modelObjectId, or a group, tag, or type selector. Runtime point lights attach to the target object and follow its transform.

Effect channels

The same channel structure works with transition, pulse, and flicker:

const channels: ViewerEffectChannels = { materials: [ { targets: "warning-light", values: { emissiveIntensity: [0, 6], opacity: [0.4, 1], }, }, ], transforms: [ { targets: "door", values: { rotation: [[0, 0, 0], [0, 1.57, 0]], }, }, ], lights: [ { lights: "warning-point-light", intensity: [0, 120], }, ], }; transition(channels, { duration: 500, easing: "ease-out" }); pulse(channels, { duration: 900, repeat: Infinity }); flicker(channels, { synchronized: false }); timeline( [ { channels, duration: 500, hold: 200 }, { channels: closingChannels, duration: 500 }, ], { repeat: Infinity }, );

When synchronized is true, every target receives the same level. When it is false, array items receive independent levels or phases while aligned channel arrays continue to describe the same logical object.

Immediate runtime operations

setMaterial("screen", { emissive: "#22d3ee", emissiveIntensity: 4, }); setMaterial({ tag: "warning" }, { emissive: "#ef4444", emissiveIntensity: 4, }); setTransform("door", { rotation: [0, Math.PI / 2, 0], position: [0, 0.2, 0], }); const position = getWorldPosition("door");

Selectors can target several bindings for material and transform operations. getWorldPosition remains a single-object lookup.

Transform rotations use radians. Immediate changes are snapshotted the first time they are applied so resetRuntime can restore the original values.

Point lights

addPointLight({ id: "status-light", target: "status-led", color: "#22c55e", intensity: 80, distance: 12, decay: 2, offset: [0, 0.02, 0], }); updatePointLight("status-light", { intensity: 140 }); removePointLight("status-light");

These lights are created and disposed by react-immersive. Application code does not import PointLight or render React Three Fiber light elements.

Returns

NameDescription
handleViewerReadyConnect the hook to ModelViewer.onViewerReady
setMaterialApply an immediate runtime material patch
setTransformApply position, rotation, scale, or visibility at runtime
getWorldPositionRead a bound object’s world position as a tuple
addPointLightCreate a point light attached to a bound object
updatePointLightUpdate or reattach a runtime point light
removePointLightDispose a runtime point light
transitionRun a finite eased channel transition
timelineSequence transitions and holds, optionally repeating
pulseRun repeating channel pulses
flickerRun synchronized or independent randomized flicker
overrideEffectTemporarily force an effect level, such as a blackout or surge
stopEffectStop one effect by ID
stopAllEffectsStop every active effect
resetRuntimeStop effects, restore snapshots, and remove runtime lights

Composing viewer-ready handlers

Use useViewerConnection when camera or other viewer hooks also need the callback:

import { useViewerCamera, useViewerConnection, useViewerEffects, } from "@liveroom-tech/react-immersive"; const camera = useViewerCamera(); const effects = useViewerEffects(); const connection = useViewerConnection(camera, effects); <ModelViewer modelUrl="/model.glb" licenseKey={licenseKey} objectBindings={objectBindings} onViewerReady={connection.handleViewerReady} />;