Skip to content

Basic Usage ​

Create an Instance ​

Option 1: Pass modelPath ​

ts
import { Live2DSprite } from 'easy-live2d'

const sprite = new Live2DSprite({
  modelPath: '/Resources/Hiyori/Hiyori.model3.json',
})

Option 2: Create First, Then init() ​

ts
const sprite = new Live2DSprite()

sprite.init({
  modelPath: '/Resources/Hiyori/Hiyori.model3.json',
  draggable: true,
})

Option 3: Use CubismSetting ​

ts
import { CubismSetting, Live2DSprite } from 'easy-live2d'

const modelJSON = await fetch('/Resources/Hiyori/Hiyori.model3.json').then(r => r.json())

const modelSetting = new CubismSetting({
  modelJSON,
  prefixPath: '/Resources/Hiyori/',
})

// Optional: rewrite asset paths (takes precedence over prefixPath)
modelSetting.redirectPath(({ file }) => {
  return `https://cdn.example.com/live2d/hiyori/${file}`
})

const sprite = new Live2DSprite({
  modelSetting,
})

You must provide at least modelPath or modelSetting. If both are present, modelPath takes precedence.

Add to a Pixi Scene ​

ts
import { Application } from 'pixi.js'

const canvas = document.getElementById('live2d') as HTMLCanvasElement
const app = new Application()

await app.init({
  canvas,
  backgroundAlpha: 0,
  autoDensity: true,
  resolution: Math.max(window.devicePixelRatio || 1, 1),
})

sprite.width = canvas.clientWidth
app.stage.addChild(sprite)

Live2DSprite extends Pixi Sprite, so all standard properties work:

ts
sprite.x = 40
sprite.y = -80
sprite.anchor.set(0.5)
sprite.scale.set(0.8)

Wait for Model Ready ​

Pixi defaults to WebGL 2; no extra renderer options or ticker are needed. Updates follow Pixi renders, with a zero first time step and a maximum 100ms step after long pauses.

The ready event fires after internal initialization completes:

ts
sprite.onLive2D('ready', () => {
  console.log('model ready')
  console.log(sprite.getModelCanvasSize())
})

Operations that depend on model state — getting the original canvas size, playing the first motion, etc. — should go in the ready callback.

TIP

Calling startMotion(), setExpression(), playVoice(), etc. before ready is safe — requests are automatically queued and executed after initialization.

Handle Loading Failures ​

To handle loading failures as well as readiness, await the stable Promise after adding the sprite to the stage:

ts
import { Priority } from 'easy-live2d'

try {
  await sprite.ready
  await sprite.startMotion({ group: 'TapBody', no: 0, priority: Priority.Normal })
} catch (error) {
  console.error('Model initialization or motion loading failed', error)
}

Failed initialization does not retry every frame. With autoStart: false, call app.render() before awaiting readiness. Calls queued before readiness do not wait for actual playback. Synchronous ready listener errors are logged without destroying a loaded model; async listener errors must be handled inside the listener.

Hit Areas ​

ts
import { Priority } from 'easy-live2d'

sprite.onLive2D('hit', async ({ hitAreaName, x, y }) => {
  console.log('hit area:', hitAreaName, x, y)

  if (hitAreaName === 'Head') {
    await sprite.startMotion({
      group: 'TapBody',
      no: 0,
      priority: Priority.Force,
    })
  }
})
  • hitAreaName comes from the model's HitAreas config.
  • x, y are transformed model-view coordinates, not DOM pixel coordinates.

Drag the Model ​

ts
const sprite = new Live2DSprite({
  modelPath: '/Resources/Hiyori/Hiyori.model3.json',
  draggable: true,
})

sprite.onLive2D('dragStart', ({ x, y }) => {
  console.log('drag start', x, y)
})

sprite.onLive2D('dragMove', ({ x, y, deltaX, deltaY }) => {
  console.log('dragging', x, y, deltaX, deltaY)
})

sprite.onLive2D('dragEnd', ({ x, y }) => {
  console.log('drag end', x, y)
})
  • Drag events only fire when draggable: true.
  • x, y in drag events are the sprite position, not pointer coordinates.

Play Motions ​

ts
import { Priority } from 'easy-live2d'

// Play a specific motion
await sprite.startMotion({
  group: 'TapBody',
  no: 0,
  priority: Priority.Normal,
})

// Play a random motion from the group
await sprite.startRandomMotion({
  group: 'TapBody',
  priority: Priority.Normal,
})

Priority values:

ValueDescription
Priority.NoneNo preemption
Priority.IdleIdle motion
Priority.NormalNormal motion
Priority.ForceForce-interrupt the current motion

After a motion finishes, the runtime falls back to the idle group specified by Config.MotionGroupIdle.

Change Expressions ​

ts
// By expressionId
sprite.setExpression({ expressionId: 'smile' })

// By index
sprite.setExpression({ index: 0 })

sprite.setRandomExpression()

A warning is logged if the expressionId or index does not exist.

Voice and Lip Sync ​

ts
// Play voice
await sprite.playVoice({
  voicePath: '/Resources/Hiyori/sounds/test.wav',
})

// immediate: true stops current voice before playing
await sprite.playVoice({
  voicePath: '/Resources/Hiyori/sounds/test.mp3',
  immediate: true,
})

// Stop voice
sprite.stopVoice()
  • Voice decoding uses Web Audio decodeAudioData — supports any browser-decodable audio format (wav, mp3, ogg, etc.).
  • Lip sync requires LipSync parameter mapping in the model.

Voice resources are isolated per sprite. Stop/destroy cancels pending voice work without interrupting other sprites; immediate: false keeps voices already playing on this sprite. Playback and lip sync share one download/decode. See the voice API for completion and failure behavior.

Size Control ​

ts
// Set width and height directly
sprite.width = 420
sprite.height = 760

// Set via setSize
sprite.setSize(420, 760)

// Get current size
const size = sprite.getSize()

// Get original model canvas size
const canvasSize = sprite.getModelCanvasSize()
// => { width, height, pixelsPerUnit } | null

width / height can be set before the model loads — they are applied automatically after initialization.

Global Config ​

ts
import { Config, LogLevel } from 'easy-live2d'

Config.MotionGroupIdle = 'Idle'
Config.MouseFollow = true
Config.DebugTouchLogEnable = false
Config.CubismLoggingLevel = LogLevel.LogLevel_Warning

Common fields:

FieldDefaultDescription
MotionGroupIdle'Idle'Idle motion group to fall back to
MouseFollowtrueModel follows mouse movement
MotionSoundtrueWhether to automatically play motion-bound sound effects when calling startMotion
DebugLogEnabletrueEnable Cubism logging
DebugTouchLogEnablefalseLog touch coordinates
CubismLoggingLevelLogLevel_VerboseCubism Framework log level
MOCConsistencyValidationEnabletrueEnable moc consistency validation

Use Config.resetConfig() to restore all defaults.

Parameter Control ​

ts
// Set a parameter value by ID (persists across frames — re-applied every render cycle)
sprite.setParameterValueById('ParamAngleX', 15.0)

// With blend weight
sprite.setParameterValueById('ParamMouthOpenY', 1.0, 0.8)

// Set a parameter value by index
sprite.setParameterValueByIndex(0, 0.5)

// Get the value range of a parameter by ID
const range = sprite.getParameterValueRangeById('ParamAngleX')
// range => { min: -30, max: 30 }

// Get the value range of a parameter by index
const rangeByIndex = sprite.getParameterValueRangeByIndex(0)
// rangeByIndex => { min: -30, max: 30 }
  • Values set via setParameterValueById / setParameterValueByIndex persist across frames — they are re-applied every render cycle without needing to be called again each frame.
  • Calls before ready are automatically queued and executed after initialization.
  • getParameterValueRangeById / getParameterValueRangeByIndex return null if the model is not ready or the parameter does not exist.

Release Resources ​

ts
sprite.destroy()

Cleans up pointer listeners, ResizeObserver, WebGL texture cache, Live2D context, and Cubism lifecycle. Call this on component unmount in Vue, React, etc.

Multiple Instances ​

Repeated destruction is safe. Destruction during loading rejects pending readiness and cancels requests; shared Framework resources remain available until the last active/loading sprite is released. Destroy the host Pixi Application separately when your application no longer needs it.

Each Live2DSprite holds its own context and event bus, supporting multiple instances in the same Pixi scene:

ts
const spriteA = new Live2DSprite({
  modelPath: '/Resources/Hiyori/Hiyori.model3.json',
})

const spriteB = new Live2DSprite({
  modelPath: '/Resources/Mark/Mark.model3.json',
})

spriteA.width = 300
spriteB.width = 300
spriteB.x = 400

app.stage.addChild(spriteA)
app.stage.addChild(spriteB)

Released under the MIT License