DocumentationJavaScript

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.

ts
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.

two.ts
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.

ts
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.

ts
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.

Vue
<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.