JavaScript
Use the React-free root API.
Create, load, dispose
The JavaScript runtime owns one Canvas. Await loading before issuing model commands, and always dispose when its host view leaves the page.
import { createLive2D } from 'live2d-web'
const character = await createLive2D({
container: document.querySelector('#avatar'),
coreUrl: '/live2dcubismcore.min.js',
src: '/models/model.model3.json',
})
await character.motion('TapBody', 0)
// When the host view is removed:
character.dispose()More than one model
addModel() loads another model onto the same canvas, so a second character
costs a model rather than a second WebGL context, which browsers cap.
const stage = await createLive2D({
container: document.querySelector('#stage')!,
coreUrl: '/live2dcubismcore.min.js',
src: '/models/host.model3.json',
})
const guest = await stage.addModel({
src: '/models/guest.model3.json',
fit: { scale: 0.8, offsetX: 0.3, units: 'stage' },
})
await guest.motion('Tap', 0)
guest.dispose()Models draw in the order they were added, so a later one sits on top. Each owns
its own fit, motions, expressions, parameters and hit areas, and disposing one
leaves the canvas and the others alone. src is optional: omit it to open an
empty canvas and fill it with addModel().
The instance keeps its own motion(), fit and the rest, which act on the
model src created. The placement overlay belongs to one
model at a time.
Canvas accessibility
Choose semantics only when the Canvas should be exposed to assistive technologies. Omit the option to preserve the default markup.
const character = await createLive2D({
accessibility: {
label: 'Animated support character',
fallbackText: 'Support character illustration',
},
container: document.querySelector('#avatar'),
src: '/models/model.model3.json',
})Use { mode: 'decorative' } when the model conveys no information. Keep tap
and motion actions available through normal keyboard-accessible DOM controls;
the runtime never adds tabIndex or role="application" to the Canvas.
Handle lifecycle and retry
Keep the returned instance in the same view that owns the container. Pause it when a hidden tab does not need animation, resume it on return, and dispose exactly when the view is destroyed. dispose() is idempotent, so cleanup may safely run after a partial setup failure.
const onVisibility = () => document.hidden ? character.pause() : character.resume()
document.addEventListener('visibilitychange', onVisibility)
window.addEventListener('pagehide', () => {
document.removeEventListener('visibilitychange', onVisibility)
character.dispose()
}, { once: true })Vue, Svelte and other frameworks
Only React has its own entry, because a component tree needs help that a lifecycle hook does not: React mounts effects twice under Strict Mode, renders concurrently, and cleans up on its own schedule. Elsewhere the imperative API is the binding. Create the instance when the component mounts, dispose it when it unmounts, and hold it in whatever your framework calls local state.
<script setup lang="ts">
import { createLive2D } from 'live2d-web'
import { onBeforeUnmount, onMounted, ref } from 'vue'
const host = ref<HTMLElement>()
let character: Awaited<ReturnType<typeof createLive2D>> | undefined
onMounted(async () => {
character = await createLive2D({
container: host.value!,
coreUrl: '/live2dcubismcore.min.js',
src: '/models/model.model3.json',
})
})
onBeforeUnmount(() => character?.dispose())
</script>
<template>
<div ref="host" />
</template>The Vue Vite example runs this in full. Svelte, Solid and Angular follow the same shape with their own lifecycle names.
Keep application controls outside Canvas
Use ordinary buttons for motions, expressions and retry. This makes keyboard behavior explicit and lets the application disable controls until loading completes. Catch Live2DError, show its stable code, and recreate the instance only after the underlying Core, asset or WebGL problem has changed.