@mymosdk/avatar 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +656 -0
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
# mymo-avatar
|
|
2
|
+
|
|
3
|
+
Lightweight animated 3D avatar SDK for any web application.
|
|
4
|
+
|
|
5
|
+
- **23 KB gzip** (core, Three.js not included)
|
|
6
|
+
- **60 FPS** WebGL rendering via Three.js
|
|
7
|
+
- **Framework-agnostic** — Vanilla, React, Vue, Next.js, Nuxt, Svelte, Electron
|
|
8
|
+
- **AI-agnostic** — works standalone or with any LLM/TTS provider
|
|
9
|
+
- **< 5 min** to integrate
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
The SDK renders an animated 3D avatar as a fixed floating widget that mounts itself directly to `document.body`. You create one `Avatar` instance and control everything through its chainable API — no framework bindings required unless you want them.
|
|
14
|
+
|
|
15
|
+
Under the hood, six systems work together:
|
|
16
|
+
|
|
17
|
+
| System | Responsibility |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **Renderer** | Three.js WebGL scene with ambient + key + fill lighting, ACES Filmic tone mapping, optional shadows |
|
|
20
|
+
| **Animation Engine** | Procedural bone rotations for gestures + morph target blending for expressions |
|
|
21
|
+
| **Audio Engine** | Web Audio API wrapper — playback, amplitude analysis, gain control |
|
|
22
|
+
| **Lip Sync** | Amplitude-to-viseme mapping; drives mouth morphs in real time |
|
|
23
|
+
| **Asset Loader** | GLB/VRM loading from CDN or any URL; in-memory model cache |
|
|
24
|
+
| **Plugin System** | `avatar.use(plugin)` registers third-party or custom integrations |
|
|
25
|
+
|
|
26
|
+
The avatar widget is a rounded `<div>` containing a `<canvas>`. The widget can be dragged, repositioned, resized, and themed at runtime without recreating it.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# npm
|
|
34
|
+
npm install @mymosdk/avatar three
|
|
35
|
+
|
|
36
|
+
# pnpm
|
|
37
|
+
pnpm add @mymosdk/avatar three
|
|
38
|
+
|
|
39
|
+
# yarn
|
|
40
|
+
yarn add @mymosdk/avatar three
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> Three.js is a peer dependency. If you already have it in your project, you only need `@mymosdk/avatar`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { Avatar } from "@mymosdk/avatar"
|
|
51
|
+
|
|
52
|
+
const avatar = new Avatar({
|
|
53
|
+
model: "https://github.com/JairoGerardo/mymo-avatar/releases/download/v0.1.0-assets/Maya.vrm",
|
|
54
|
+
position: "bottom-right",
|
|
55
|
+
size: 200,
|
|
56
|
+
theme: "dark",
|
|
57
|
+
framing: "bust",
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The avatar mounts itself to `document.body` as a fixed floating widget immediately.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## API
|
|
66
|
+
|
|
67
|
+
### Constructor options
|
|
68
|
+
|
|
69
|
+
| Option | Type | Default | Description |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `model` | `string` | — | GLB or VRM URL to load on init |
|
|
72
|
+
| `position` | `AvatarPosition` | `"bottom-right"` | Corner preset |
|
|
73
|
+
| `size` | `number` | `180` | Widget size in px |
|
|
74
|
+
| `theme` | `AvatarTheme` | `"light"` | Background theme |
|
|
75
|
+
| `themeConfig` | `ThemeConfig` | `{}` | Custom background/shadow per theme |
|
|
76
|
+
| `framing` | `AvatarFraming` | `"full"` | Camera framing preset |
|
|
77
|
+
| `framingConfig` | `FramingSliceConfig` | `{}` | Per-mode framing fine-tuning |
|
|
78
|
+
| `draggable` | `boolean` | `false` | Allow drag & drop repositioning |
|
|
79
|
+
| `shadows` | `boolean` | `false` | Enable shadow rendering |
|
|
80
|
+
| `idle` | `boolean` | `true` | Enable idle micro-expression animations |
|
|
81
|
+
| `idleInterval` | `number` | `8000` | Idle animation interval (ms) |
|
|
82
|
+
| `blink` | `boolean` | `true` | Enable auto-blink |
|
|
83
|
+
| `blinkInterval` | `number` | `3500` | Blink interval (ms) |
|
|
84
|
+
| `lipSync` | `boolean` | `true` | Enable audio-driven lip sync |
|
|
85
|
+
| `followMouse` | `boolean` | `false` | Auto-track mouse cursor |
|
|
86
|
+
| `autoHide` | `boolean` | `false` | Hide widget when user scrolls |
|
|
87
|
+
| `zIndex` | `number` | `99999` | CSS z-index of the widget |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### Framing
|
|
92
|
+
|
|
93
|
+
Control how much of the model is visible in the widget:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
avatar.frame("full") // entire body
|
|
97
|
+
avatar.frame("half") // waist up
|
|
98
|
+
avatar.frame("bust") // chest up
|
|
99
|
+
avatar.frame("face") // head only
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Fine-tune each mode at runtime:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
avatar.setFramingConfig({
|
|
106
|
+
bust: { from: 0.70, to: 1.0, lookBias: 0.44 },
|
|
107
|
+
face: { from: 0.82, to: 1.0, lookBias: 0.34 },
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Field | Type | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `from` | `number` | Bottom of visible slice (0–1) |
|
|
114
|
+
| `to` | `number` | Top of visible slice (0–1, usually 1.0) |
|
|
115
|
+
| `lookBias` | `number` | Where the camera aims in the slice (0.5 = center; lower = more headroom) |
|
|
116
|
+
|
|
117
|
+
Default slices:
|
|
118
|
+
|
|
119
|
+
| Mode | `from` | `to` | `lookBias` |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| `full` | 0.00 | 1.00 | 0.50 |
|
|
122
|
+
| `half` | 0.48 | 1.00 | 0.50 |
|
|
123
|
+
| `bust` | 0.68 | 1.00 | 0.46 |
|
|
124
|
+
| `face` | 0.80 | 1.00 | 0.36 |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Theme
|
|
129
|
+
|
|
130
|
+
Switch theme at runtime or customize colors and shadows:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
avatar.setTheme("dark")
|
|
134
|
+
|
|
135
|
+
avatar.setThemeConfig({
|
|
136
|
+
light: {
|
|
137
|
+
background: "radial-gradient(circle at 60% 40%, #ffffff, #e8e8e8)",
|
|
138
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
|
|
139
|
+
},
|
|
140
|
+
dark: {
|
|
141
|
+
background: "radial-gradient(circle at 60% 40%, #2a2a2a, #1a1a1a)",
|
|
142
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`ThemeConfig` is `Partial<Record<"light" | "dark", { background?: string; boxShadow?: string }>>`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### Expressions
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
avatar.smile()
|
|
155
|
+
avatar.sad()
|
|
156
|
+
avatar.happy()
|
|
157
|
+
avatar.angry()
|
|
158
|
+
avatar.surprised()
|
|
159
|
+
avatar.thinking()
|
|
160
|
+
avatar.confused()
|
|
161
|
+
avatar.sleep()
|
|
162
|
+
avatar.idle() // reset to neutral
|
|
163
|
+
|
|
164
|
+
// With explicit intensity (0–1)
|
|
165
|
+
avatar.expression("smile", 0.8)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### Gestures
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
avatar.wave()
|
|
174
|
+
avatar.nod()
|
|
175
|
+
avatar.yes() // affirmative nods (larger amplitude)
|
|
176
|
+
avatar.no() // deliberate side-to-side shakes
|
|
177
|
+
avatar.shakeHead()
|
|
178
|
+
avatar.clap()
|
|
179
|
+
avatar.jump()
|
|
180
|
+
avatar.dance() // hip sway with counter-rotating spine
|
|
181
|
+
avatar.thumbsUp()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Gestures support **queuing**: if a gesture is triggered while another is active, the current one accelerates to its exit phase and the new one starts immediately after. Only one gesture can be pending at a time.
|
|
185
|
+
|
|
186
|
+
Gestures are **procedural** — they animate bones directly, so no embedded animation clips are needed in the model file.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### States (UI feedback)
|
|
191
|
+
|
|
192
|
+
States combine an expression + animation + a pulsing color ring:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
avatar.loading() // thinking expression + blue ring
|
|
196
|
+
avatar.success() // happy expression + Yes animation + green ring
|
|
197
|
+
avatar.error() // sad expression + No animation + red ring
|
|
198
|
+
avatar.warning() // surprised expression + orange ring
|
|
199
|
+
avatar.typing() // thinking expression, no ring
|
|
200
|
+
avatar.listening() // smile expression + blue ring
|
|
201
|
+
avatar.processing() // thinking expression + blue ring
|
|
202
|
+
avatar.complete() // happy expression + ThumbsUp animation + green ring
|
|
203
|
+
|
|
204
|
+
avatar.setState("listening") // generic setter
|
|
205
|
+
avatar.clearState() // reset expression, animation, and ring
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### Speech & Lip Sync
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// Play audio with automatic lip sync (AudioBuffer, ArrayBuffer, or URL)
|
|
214
|
+
await avatar.talk(audioBuffer)
|
|
215
|
+
|
|
216
|
+
// Manual control
|
|
217
|
+
avatar.startTalking()
|
|
218
|
+
avatar.stopTalking()
|
|
219
|
+
avatar.pauseTalking()
|
|
220
|
+
|
|
221
|
+
// Explicit phoneme/mouth control
|
|
222
|
+
avatar.setViseme("aa") // phoneme-level lip sync
|
|
223
|
+
avatar.setMouth(0.6) // direct mouth openness: 0 = closed, 1 = fully open
|
|
224
|
+
avatar.setVolume(0.8) // amplitude-driven mouth movement
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Supported visemes: `"sil"` `"PP"` `"FF"` `"TH"` `"DD"` `"kk"` `"CH"` `"SS"` `"nn"` `"RR"` `"aa"` `"E"` `"ih"` `"oh"` `"ou"`
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Look
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
avatar.lookAtMouse() // follow mouse cursor
|
|
235
|
+
avatar.lookAt(400, 300) // look at screen coordinates (px)
|
|
236
|
+
avatar.lookForward() // return to center
|
|
237
|
+
avatar.randomLook() // random periodic look
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### Visibility & Layout
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
avatar.show()
|
|
246
|
+
avatar.hide()
|
|
247
|
+
avatar.move(100, 200) // absolute position (px from top-left)
|
|
248
|
+
avatar.position("top-left") // corner preset
|
|
249
|
+
avatar.size(240) // resize widget
|
|
250
|
+
avatar.scale(1.2) // relative scale
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### Animations
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
avatar.play("Wave") // play a named clip from the GLB/VRM file
|
|
259
|
+
avatar.stop()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Events
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
avatar.on("loaded", () => console.log("avatar ready"))
|
|
268
|
+
avatar.on("modelLoaded", (_, { model }) => console.log("model:", model))
|
|
269
|
+
avatar.on("click", () => avatar.wave())
|
|
270
|
+
avatar.on("speechStart", () => console.log("talking…"))
|
|
271
|
+
avatar.on("speechEnd", () => console.log("done"))
|
|
272
|
+
avatar.on("animationStart", (_, data) => console.log(data))
|
|
273
|
+
avatar.on("animationEnd", () => {})
|
|
274
|
+
|
|
275
|
+
avatar.off("click", handler) // remove a specific listener
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Available events: `"click"` `"loaded"` `"modelLoaded"` `"animationStart"` `"animationEnd"` `"speechStart"` `"speechEnd"`
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Load models
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
// By direct URL — GLB and VRM both supported
|
|
286
|
+
await avatar.load("https://example.com/my-avatar.glb")
|
|
287
|
+
await avatar.load("https://example.com/my-avatar.vrm")
|
|
288
|
+
|
|
289
|
+
// Local file
|
|
290
|
+
await avatar.load("/models/my-avatar.vrm")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### Lifecycle
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
avatar.destroy() // removes widget from DOM, stops all loops
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
> All methods except `destroy()` are chainable.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Plugins
|
|
306
|
+
|
|
307
|
+
### TTS integration (manual)
|
|
308
|
+
|
|
309
|
+
The SDK has no built-in TTS provider — call any TTS service from your **backend**, pass the audio to `avatar.talk()`, and lip sync works automatically.
|
|
310
|
+
|
|
311
|
+
Example with ElevenLabs:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// Your backend endpoint (Express, Next.js API Route, Edge Function, etc.)
|
|
315
|
+
// POST /api/speak { text: string } → returns audio/mpeg
|
|
316
|
+
|
|
317
|
+
// Frontend
|
|
318
|
+
async function speak(text: string) {
|
|
319
|
+
avatar.setState("loading")
|
|
320
|
+
|
|
321
|
+
const res = await fetch("/api/speak", {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: { "Content-Type": "application/json" },
|
|
324
|
+
body: JSON.stringify({ text }),
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
if (!res.ok) {
|
|
328
|
+
avatar.setState("error")
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const audio = await res.arrayBuffer()
|
|
333
|
+
avatar.clearState()
|
|
334
|
+
await avatar.talk(audio) // lip sync kicks in automatically
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Keeping the TTS call on the backend means your API key is never exposed to the browser. The pattern is the same for any provider: OpenAI TTS, Azure Speech, Google Cloud TTS, etc.
|
|
339
|
+
|
|
340
|
+
### Build your own plugin
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import type { AvatarPlugin } from "@mymosdk/avatar"
|
|
344
|
+
|
|
345
|
+
const myPlugin: AvatarPlugin = {
|
|
346
|
+
name: "my-plugin",
|
|
347
|
+
install(avatar) {
|
|
348
|
+
avatar.on("click", () => avatar.wave())
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
avatar.use(myPlugin)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The `install` function receives the full `AvatarApi` (all public methods). Plugins are registered once and live for the lifetime of the avatar.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Framework integrations
|
|
360
|
+
|
|
361
|
+
### Vanilla JS / TypeScript
|
|
362
|
+
|
|
363
|
+
No adapter needed — import and go:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { Avatar } from "@mymosdk/avatar"
|
|
367
|
+
|
|
368
|
+
const avatar = new Avatar({ model: "https://example.com/my-avatar.vrm", framing: "bust", theme: "dark" })
|
|
369
|
+
|
|
370
|
+
document.querySelector("#wave-btn")?.addEventListener("click", () => avatar.wave())
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
### React
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
npm install @mymosdk/avatar three
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Hook**
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import { useAvatar } from "@mymosdk/avatar/react"
|
|
385
|
+
|
|
386
|
+
function MyComponent() {
|
|
387
|
+
const avatarRef = useAvatar({
|
|
388
|
+
model: "https://...",
|
|
389
|
+
position: "bottom-right",
|
|
390
|
+
framing: "bust",
|
|
391
|
+
idle: true,
|
|
392
|
+
blink: true,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return <button onClick={() => avatarRef.current?.wave()}>Wave</button>
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`useAvatar` creates the avatar on mount and calls `destroy()` on unmount. The returned `RefObject<Avatar>` gives you the full imperative API.
|
|
400
|
+
|
|
401
|
+
**Component**
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
import { useRef } from "react"
|
|
405
|
+
import { AvatarWidget } from "@mymosdk/avatar/react"
|
|
406
|
+
import type { Avatar } from "@mymosdk/avatar"
|
|
407
|
+
|
|
408
|
+
export function App() {
|
|
409
|
+
const ref = useRef<Avatar | null>(null)
|
|
410
|
+
|
|
411
|
+
return (
|
|
412
|
+
<>
|
|
413
|
+
<AvatarWidget
|
|
414
|
+
ref={ref}
|
|
415
|
+
model="https://..."
|
|
416
|
+
position="bottom-right"
|
|
417
|
+
theme="dark"
|
|
418
|
+
framing="bust"
|
|
419
|
+
idle
|
|
420
|
+
blink
|
|
421
|
+
draggable
|
|
422
|
+
/>
|
|
423
|
+
<button onClick={() => ref.current?.smile()}>Smile</button>
|
|
424
|
+
</>
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
`AvatarWidget` renders no DOM output — it mounts the avatar directly to `document.body`. Control it via the forwarded ref.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### Vue 3
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
npm install @mymosdk/avatar three
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Composable**
|
|
440
|
+
|
|
441
|
+
```vue
|
|
442
|
+
<script setup lang="ts">
|
|
443
|
+
import { useAvatar } from "@mymosdk/avatar/vue"
|
|
444
|
+
|
|
445
|
+
const avatar = useAvatar({
|
|
446
|
+
model: "https://...",
|
|
447
|
+
position: "bottom-right",
|
|
448
|
+
framing: "bust",
|
|
449
|
+
idle: true,
|
|
450
|
+
blink: true,
|
|
451
|
+
})
|
|
452
|
+
// avatar is a ShallowRef<Avatar | null>
|
|
453
|
+
</script>
|
|
454
|
+
|
|
455
|
+
<template>
|
|
456
|
+
<button @click="avatar.value?.wave()">Wave</button>
|
|
457
|
+
</template>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`useAvatar` creates the avatar in `onMounted` and destroys it in `onUnmounted`.
|
|
461
|
+
|
|
462
|
+
**Component**
|
|
463
|
+
|
|
464
|
+
```vue
|
|
465
|
+
<script setup lang="ts">
|
|
466
|
+
import { ref } from "vue"
|
|
467
|
+
import { AvatarWidget } from "@mymosdk/avatar/vue"
|
|
468
|
+
|
|
469
|
+
const widgetRef = ref()
|
|
470
|
+
const avatar = () => widgetRef.value?.avatar.value
|
|
471
|
+
</script>
|
|
472
|
+
|
|
473
|
+
<template>
|
|
474
|
+
<AvatarWidget
|
|
475
|
+
ref="widgetRef"
|
|
476
|
+
model="https://..."
|
|
477
|
+
position="bottom-right"
|
|
478
|
+
theme="dark"
|
|
479
|
+
framing="bust"
|
|
480
|
+
:idle="true"
|
|
481
|
+
:blink="true"
|
|
482
|
+
:draggable="true"
|
|
483
|
+
/>
|
|
484
|
+
<button @click="avatar()?.wave()">Wave</button>
|
|
485
|
+
</template>
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Access the avatar instance via `widgetRef.value.avatar.value` (the component exposes a `avatar` ShallowRef).
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
### Next.js
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
npm install @mymosdk/avatar three
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
The avatar uses WebGL and `document.body`, so it must run **client-side only**. Always use `dynamic` with `ssr: false`.
|
|
499
|
+
|
|
500
|
+
**App Router (recommended)**
|
|
501
|
+
|
|
502
|
+
```tsx
|
|
503
|
+
// app/components/AvatarDemo.tsx
|
|
504
|
+
"use client"
|
|
505
|
+
|
|
506
|
+
import { useAvatar } from "@mymosdk/avatar/react"
|
|
507
|
+
|
|
508
|
+
export default function AvatarDemo() {
|
|
509
|
+
const avatarRef = useAvatar({
|
|
510
|
+
model: "https://...",
|
|
511
|
+
position: "bottom-right",
|
|
512
|
+
framing: "bust",
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
return <button onClick={() => avatarRef.current?.wave()}>Wave</button>
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
// app/page.tsx
|
|
521
|
+
import dynamic from "next/dynamic"
|
|
522
|
+
|
|
523
|
+
const AvatarDemo = dynamic(() => import("./components/AvatarDemo"), { ssr: false })
|
|
524
|
+
|
|
525
|
+
export default function Page() {
|
|
526
|
+
return <AvatarDemo />
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Add `transpilePackages` to `next.config.ts`:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import type { NextConfig } from "next"
|
|
534
|
+
|
|
535
|
+
const nextConfig: NextConfig = {
|
|
536
|
+
transpilePackages: ["@mymosdk/avatar"],
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export default nextConfig
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Pages Router**
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
// pages/index.tsx
|
|
546
|
+
import dynamic from "next/dynamic"
|
|
547
|
+
|
|
548
|
+
const AvatarWidget = dynamic(
|
|
549
|
+
() => import("@mymosdk/avatar/react").then(m => m.AvatarWidget),
|
|
550
|
+
{ ssr: false }
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
export default function Home() {
|
|
554
|
+
return (
|
|
555
|
+
<AvatarWidget
|
|
556
|
+
model="https://..."
|
|
557
|
+
position="bottom-right"
|
|
558
|
+
framing="bust"
|
|
559
|
+
theme="dark"
|
|
560
|
+
/>
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
### CDN (UMD)
|
|
568
|
+
|
|
569
|
+
No build step required:
|
|
570
|
+
|
|
571
|
+
```html
|
|
572
|
+
<script src="https://unpkg.com/three@0.165.0/build/three.min.js"></script>
|
|
573
|
+
<script src="https://unpkg.com/@mymosdk/avatar/dist/mymo-avatar.umd.js"></script>
|
|
574
|
+
<script>
|
|
575
|
+
const { Avatar } = MymoAvatar
|
|
576
|
+
const avatar = new Avatar({ model: "https://example.com/my-avatar.vrm", theme: "dark", framing: "bust" })
|
|
577
|
+
</script>
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Model formats
|
|
583
|
+
|
|
584
|
+
### VRM — recommended
|
|
585
|
+
|
|
586
|
+
VRM is the first-class format. All features work out of the box on any valid VRM file because the spec enforces standardized bone names and blendshapes. Gestures are procedurally animated — no embedded animation clips required.
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
await avatar.load("https://example.com/my-avatar.vrm")
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Good sources: [VRoid Studio](https://vroid.com/en/studio), [VRoid Hub](https://hub.vroid.com), [Booth](https://booth.pm).
|
|
593
|
+
|
|
594
|
+
### GLB — best-effort
|
|
595
|
+
|
|
596
|
+
GLB files are supported but feature availability depends on what the model includes. A `console.warn` is emitted at load time. For each feature to work, the model must ship the corresponding data:
|
|
597
|
+
|
|
598
|
+
| Feature | Requirement |
|
|
599
|
+
|---|---|
|
|
600
|
+
| Expressions | Morph targets named `Smile`, `Sad`, `Happy`, `Angry`, etc. |
|
|
601
|
+
| Lip sync | Morph targets: `mouthOpen`, `jawOpen`, or `viseme_*` |
|
|
602
|
+
| Blink | Morph targets: `Blink` or `eyesClosed` |
|
|
603
|
+
| Gestures | Animation clips: `Wave`, `Nod`, `Dance`, etc. |
|
|
604
|
+
| Head look | Bone named `Head`, `head`, or `mixamorigHead` |
|
|
605
|
+
|
|
606
|
+
Missing features degrade gracefully and are skipped silently after the initial load warning.
|
|
607
|
+
|
|
608
|
+
### Built-in models
|
|
609
|
+
|
|
610
|
+
> **Coming in v1.1.** Named models (`maya`, `robot`, `fox`) will be published as separate npm packages (`@mymo/model-maya`, etc.) and resolved from CDN automatically. Use a direct URL in the meantime:
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
// Use any GLB or VRM by URL today
|
|
614
|
+
await avatar.load("https://example.com/my-avatar.vrm")
|
|
615
|
+
await avatar.load("https://example.com/my-avatar.glb")
|
|
616
|
+
await avatar.load("/models/local-avatar.vrm") // local file
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Good free VRM sources: [VRoid Hub](https://hub.vroid.com), [Booth](https://booth.pm).
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## TypeScript
|
|
624
|
+
|
|
625
|
+
All types are exported from `@mymosdk/avatar`:
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
import type {
|
|
629
|
+
AvatarOptions,
|
|
630
|
+
AvatarPosition, // "bottom-right" | "bottom-left" | "top-right" | "top-left"
|
|
631
|
+
AvatarTheme, // "light" | "dark" | "transparent"
|
|
632
|
+
AvatarFraming, // "full" | "half" | "bust" | "face"
|
|
633
|
+
AvatarEvent, // "click" | "loaded" | "modelLoaded" | "animationStart" | "animationEnd" | "speechStart" | "speechEnd"
|
|
634
|
+
AvatarState, // "loading" | "success" | "error" | "warning" | "typing" | "listening" | "processing" | "complete"
|
|
635
|
+
AvatarPlugin,
|
|
636
|
+
AvatarApi,
|
|
637
|
+
Expression, // "smile" | "sad" | "happy" | "angry" | "surprised" | "thinking" | "confused" | "sleep" | "idle"
|
|
638
|
+
Gesture, // "wave" | "nod" | "yes" | "no" | "shakeHead" | "clap" | "jump" | "dance" | "thumbsUp"
|
|
639
|
+
Viseme,
|
|
640
|
+
FramingSliceConfig,
|
|
641
|
+
FramingModeConfig,
|
|
642
|
+
ThemeConfig,
|
|
643
|
+
} from "@mymosdk/avatar"
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## Browser support
|
|
649
|
+
|
|
650
|
+
Chrome 90+, Firefox 88+, Safari 15+, Edge 90+
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## License
|
|
655
|
+
|
|
656
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mymosdk/avatar",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Lightweight animated avatar SDK for any web application",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"avatar",
|
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
"homepage": "https://github.com/JairoGerardo/mymo-avatar#readme",
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
|
-
"
|
|
42
|
+
"README.md"
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "vite build && vite build --config vite.umd.config.ts && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
46
|
+
"prepack": "node -e \"require('fs').copyFileSync('../../README.md', 'README.md')\"",
|
|
46
47
|
"dev": "vite build --watch",
|
|
47
48
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
48
49
|
"test": "vitest run",
|