Rendering
Charivo's rendering layer is usually built from @charivo/render and
@charivo/render-live2d.
Recommended Stack
Use:
@charivo/render-live2das the concrete renderer@charivo/renderas the stateful manager
This is the default rendering path in the repo.
What Each Package Does
@charivo/render-live2ddraws the Live2D model and exposes renderer methods@charivo/renderconnects the renderer to Charivo events, mouse tracking, canonical avatar actions, and lip-sync updates
Most apps use both together.
Basic Setup
import { createLive2DRenderer } from "@charivo/render-live2d";
import { createRenderManager } from "@charivo/render";
const renderer = createLive2DRenderer({ canvas });
const renderManager = createRenderManager(renderer, {
canvas,
mouseTracking: "document",
});
await renderManager.initialize();
await renderManager.loadModel?.("/live2d/Hiyori/Hiyori.model3.json");
Attach the manager to Charivo after initialization:
charivo.attachRenderer(renderManager);
What @charivo/render Adds
- character text rendering
tts:audio:startandtts:audio:endtts:lipsync:updateavatar:expressionavatar:motion— may carrymuteSound, which plays the motion without its baked-in sample clip.@charivo/avatarsets it on tool-call motions so a Cubism sample's prerecorded voice never talks over the character's own; without the flag the clip stays audible.avatar:gaze- optional mouse tracking
In normal app code, wire the manager to Charivo rather than handling these
events yourself.
Expression Auto-Release
An expression triggered via avatar:expression is released when
tts:audio:end arrives — however long the utterance runs — and the model fades
back to its base face over the expression's FadeOutTime (1 second when the
.exp3.json does not set one) instead of snapping.
A roughly 8-second timer is the fallback for setups where no audio events flow
at all, such as text-only configurations that never emit tts:audio:start /
tts:audio:end. It is disarmed while an utterance is playing, so it never
competes with speech: it fires only before speech starts, or where it is the
sole release trigger.
Three qualifications:
- Parameters using the
AddorMultiplyblend fade smoothly when their release duration is positive — an authoredFadeOutTime: 0is honored as an instant release, a knob model authors have. Parameters usingOverwritesnap to their base value instead, because the Cubism SDK rebases overwrite values from the model every frame (uncommon in practice: none of the bundled demo models useOverwrite). - A release requested before the expression has finished fading in — a long
authored
FadeInTime, or rendering paused in a hidden tab — waits for the fade-in to complete and then fades, so an expression can remain visible beyond the nominal 8-second hold. - This applies when the renderer implements
stopExpression.@charivo/render-live2ddoes; a renderer that omits it keeps the expression until something else replaces it.
Idle motion, eye blink, and breath keep running throughout.
Event Wiring
RenderManager intentionally uses setEventBus(...), not
setEventEmitter(...).
Rendering subscribes to upstream events, so it needs the full event bus
contract. This split is part of the public design. Detaching or replacing the
renderer tears down its bus subscriptions and releases any held expression via
disconnect(), so a detached manager stops reacting to events without being
destroyed.
Model Loading
The usual flow is:
- create the canvas
- create the renderer
- create the render manager
- call
initialize() - call
loadModel(...) - attach the manager to
Charivo
If the renderer exposes optional methods such as playExpression,
stopExpression, playMotionByGroup, lookAt, or model catalog getters,
@charivo/render will use them automatically.
Gaze Drivers
The render manager arbitrates between three gaze drivers:
- AI gaze — driven by the
avatar:gazeevent from the realtime model's look intent. Owns the avatar's gaze while the AI suspend window is active. - Local-presence gaze — driven by calling
renderManager.setLocalGaze(coords)from the app layer (e.g. webcam face tracking). Suspends mouse cursor tracking through a separate local-gaze window while active. - Mouse cursor — the default continuous mouse-tracking path.
Priority for cursor-follow: AI (avatar:gaze) > local-presence (setLocalGaze) > mouse cursor
Deliberate taps yield only to the AI window — local-presence does not suppress them.
setLocalGaze returns false (no-op) while AI gaze owns the avatar or when
the renderer has no lookAt.
Alternatives
- Use
@charivo/render/stubfor tests or demos that do not need real rendering. - If you already have a custom renderer, keep using
@charivo/renderand provide aRendererimplementation from@charivo/core.