简介:这是一个演示HTML页面集成Live2D的完整demo压缩包,面向想在网页中加入二维动态角色的前端开发者和互动设计师,帮助解决从零接入Live2D、模型加载、交互响应的常见问题。资源包共612个文件,压缩后仅17.29MB,其中包含22个moc模型源文件、158个json配置元数据、307个mtn动作数据、51张png贴图以及44个mp3语音,另有2个html页面和2个js脚本作为入口与逻辑层,1个md说明文档提供指引。从文件构成可清晰看到Live2D项目的标准目录分工。demo完整覆盖动态更换人物、点击或触摸模型不同部位触发表情与动作变化、通过按钮等页面组件联动角色表演等关键机制,能直观理解模型初始化、Canvas渲染、事件监听与重新配置模型之间的调用关系。目前已有1433人学习下载,适合需要快速把Live2D落地到浏览器中的初中级开发者反复研读。
1. 在网页里养一只会动的看板娘:html集成live2D demo到底能做什么
html 集成 live2D demo,用一句话说,就是在一个网页页面里加载并运行 Live2D 虚拟角色,让它能展示动画、响应交互。第一次在别人博客右下角看到 Live2D 看板娘时,我盯着那个会眨眼、会歪头的小人看了好一会儿——网页居然能养这么灵动的角色。这个技术方向解决两类具体诉求:一是给个人网站、产品落地页或工具页面加一个拟人化角色,提升互动感;二是做技术验证,确认某个模型能不能在你的页面环境里跑起来、性能开销可不可控。门槛不高,但散落着不少坑。很多教程只给你看效果图,不告诉你模型为什么加载不出来、跨域怎么处理、内存怎么会爆。下面按从零到能跑、再到能用的路径捋一遍,都是实际趟过路的做法,新手能照着操作,老手也能对齐几个常见陷阱。
2. 先把渲染这条路走对:Live2D 网页集成的两种主流方案
2.1 摸清 Live2D 模型的文件结构,才能在 404 时迅速定位
拿到一个 Live2D 模型压缩包时,解压后通常不是单个文件,而是带着配置入口、几何数据、贴图和动画动作的一整套资源。一个典型 Cubism 4 模型的内部结构如下:
| 文件 / 目录 | 作用 | 加载阶段 |
|---|---|---|
xxx.model3.json | 模型入口,声明所有外部资源引用关系 | SDK 从这里开始读 |
xxx.moc3 | 模型网格与变形数据 | model3.json 找到它后加载 |
textures/ | 一张或多张贴图,对应模型皮肤层 | 与模型绑定,缺了局部贴图就空 |
expressions/*.json | 表情预设参数 | 触发表情时用到 |
motions/*.motion3.json | 动作动画,挥手、点头、眨眼等 | 调用 motion 时播放 |
physics.json | 物理模拟参数,头发、裙摆摆动 | 每帧参与物理运算 |
在这套文件里,最容易被坑的就是试图直接加载.moc3。早期 Live2D 1.x 版本确实是直接读 moc,但从 Cubism 3 之后,模型强依赖model3.json这个入口来解析资源关系。在 html 页面里填给 SDK 的 URL 应该是xx.model3.json,而不是.moc3。填错后 SDK 往往不会给出友好提示,而是卡在加载阶段,页面只剩一个透明 canvas。
由此延伸出一个排查习惯:凡是模型加载失败,先打开浏览器的 Network 面板,看请求列表里哪个文件返回 404。如果 model3.json 这一层就挂掉,后面的 moc3、textures 都无从加载;如果只是某张贴图 404,模型能显示但会有局部裂图。所以拿到一个模型包,我会先看文件结构,再决定路径写法。
社区里有些被精简过的"基础模型包",只保留了 moc3 和贴图,没有 motions 和 physics。这类包能显示但不会眨眼,也没有物理摆动,交互体验大打折扣。判断一个模型是否完整,最直接的方法是看它有没有 model3.json 入口文件,以及入口 JSON 里是否指向了 motions 和 physics。如果两个都没有,就别指望它能在页面上"活"起来。
2.2 方案一:官方 Cubism SDK 功能最全,但上手成本偏高
官方 Cubism Web SDK 分两层:live2dcubismcore是编译好的底层核心,负责解析 moc3、执行变形和物理计算;上层 Framework 是一套 TypeScript 类库,包装模型实例创建、参数更新、动作表情管理。两层配合才能跑起来。
使用官方 SDK,常见做法是:用 npm 安装 Core 包,把 Framework 源码放进工程目录,自己创建 WebGL 上下文并交给渲染器,最后在 requestAnimationFrame 回调里逐帧更新模型参数。一个最小初始化骨架:
import { Live2DCubismCore } from 'live2dcubismcore'; const canvas = document.getElementById('canvas') as HTMLCanvasElement; const gl = canvas.getContext('webgl'); if (!gl) throw new Error('WebGL 不可用,请更换浏览器或开启硬件加速');这段代码只是拿到了 canvas 的 WebGL 上下文。真正把模型挂上去,还要创建 CubismUserModel 子类、实例化模型管理器、每帧调用 preDraw() 和 update(),以及处理模型资源释放。这个骨架我在不同版本的 SDK 里写过几遍,每次升级都要搬一些类名。这也是后来转用封装库的原因——官方方案的能力边界最大,能精确到每个参数做表情控制,但学习曲线和版本维护成本摆在那里。
如果你的目标不是"页面角落站一个小人",而是深度改造模型的行为逻辑或做自定义渲染管线,那官方 SDK 值得投入。你会需要操作 CubismModel 的内部参数句柄,按 ParameterId 做参数控制。注意 Cubism 4 迁移时,部分老 API 已被标记为过期,照着抄旧项目代码很容易踩坑。
2.3 方案二:pixi-live2d-display,贴近实战的封装路线
社区里做 html 集成 live2D demo,最主流的路线是pixi-live2d-display。它把 live2dcubismcore 和 PixiJS 渲染管线粘在一起,对外暴露的 API 更像操作一个场景中的精灵对象。安装命令:
npm install pixi.js pixi-live2d-display初始化逻辑比官方 SDK 短得多:
import { Application } from 'pixi.js'; import { Live2DModel } from 'pixi-live2d-display'; const app = new Application({ view: document.getElementById('live2d-canvas') as HTMLCanvasElement, width: 400, height: 600, transparent: true }); const model = await Live2DModel.from('/models/haru/haru.model3.json'); app.stage.addChild(model);这里的from()返回带真实尺寸的模型显示对象。加载完成后app.stage.addChild(model)把它加入 PixiJS 渲染树。之后 Pixi 的 ticker(帧循环)会负责每帧重绘,不需要手工写 requestAnimationFrame。
参数说明:view绑定已存在的 canvas;width/height决定画布逻辑尺寸;transparent保持 true 时,画布不画底色,模型就能浮在页面内容上方,这是看板娘场景的标准配置。要是 canvas 的 width 属性与 CSS 里设置的显示尺寸不一致,模型会出现长宽比变形,这个细节后面避坑章会再提。
两条路线对比:
| 维度 | 官方 SDK | pixi-live2d-display |
|---|---|---|
| 学习曲线 | 高,需理解底层渲染管线 | 中低,有 PixiJS 基础即可 |
| API 稳定性 | 随版本变动大 | 较稳定,社区使用面大 |
| 性能控制 | 每帧细节完全可控 | 依赖 Pixi 的渲染管理 |
| 适合场景 | 深度二开、自定义渲染 | 快速集成、页面看板娘 |
我的选择经验是:demo 和常规页面用 pixi-live2d-display 快速落地,产品化遇到底层性能瓶颈再研究官方 SDK。别一上来就陷入底层,先让角色动起来才是关键。
3. 把第一个 html 集成 live2D demo 跑起来:最小可复现的 Vite 工程
3.1 准备模型资源:从哪找、往哪放、怎么验
做 demo 之前得先有一份模型文件。常见来源有两个:一是官方 Cubism SDK 包里自带的示例模型,比如 haru、miku 这类角色,它们随 SDK 一起发布,文件结构完整;二是社区流传的模型合集,很多时候以live2d-master.zip这样的 zip 包形式分发,解压后是一个 models 目录,按角色拆成子文件夹。
拿到模型后,把整个角色文件夹放进项目的public/models/目录。Vite 会把public/下的内容原样映射到服务器根路径,所以/models/haru/haru.model3.json这个 URL 就能直接访问到文件。放好之后,在浏览器地址栏手动输入这个 URL,能打开 JSON 就说明资源路径没问题。
这里有个判断模型完整度的小技巧:模型包里如果只有.moc3和纹理贴图,没有motions/目录或physics.json,加载后只会有一个静态或轻微呼吸的角色,没有眨眼和物理摆动。demo 阶段建议选官方示例模型,至少它结构完整。检查方式很简单:看 model3.json 的 JSON 内容里有没有FileReferences字段下的 Files 列表,是否指向了 motions、expressions、physics 等文件。
3.2 搭建 Vite 工程并安装依赖
我在做这种单页 demo 时,习惯用 Vite 的 vanilla 模板,因为它自带开发服务器和打包配置,省掉手工配置模块解析的步骤,也不用担心 CDN 连接不稳定导致模型和依赖加载失败。
npm create vite@latest live2d-demo -- --template vanilla cd live2d-demo npm install npm install pixi.js pixi-live2d-display命令说明:第一条创建名为live2d-demo的 Vite 项目,模板选 vanilla 纯 JavaScript,不带框架,方便把注意力集中在 Live2D 本身;后面三条分别是进入目录、安装基础依赖、安装渲染库和 Live2D 适配层。
安装完成后,开发服务器会在本地起一个静态服务。public/目录映射为根路径,模型只需放在public/models/下,就能用根路径形式访问,这也是我在上一节强调public目录的原因。
3.3 编写 index.html:一个 canvas 加一行容器
这页不需要复杂交互,一个 canvas 就够。HTML 文件里把 canvas 固定在页面右下角,模拟最常见的"看板娘"挂载位置。
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Live2D 看板娘 Demo</title> <style> body { margin: 0; background: #f5f5f5; min-height: 100vh; } #live2d-canvas { position: fixed; right: 20px; bottom: 20px; width: 300px; height: 450px; cursor: pointer; z-index: 999; } </style> </head> <body> <canvas id="live2d-canvas" width="300" height="450"></canvas> <script type="module" src="/src/main.js"></script> </body> </html>代码逻辑:页面里只有一个 canvas 元素,它的width和height属性是画布内部的逻辑像素尺寸,CSS 里又用width: 300px定义了它在页面上的显示尺寸。这里两者保持一致,避免画面被拉伸。position: fixed与right、bottom组合把画布钉在右下角,z-index: 999确保它浮在内容上方。
注意:canvas 的width属性与 CSSwidth如果不同,比如画布是 400x600 但 CSS 缩放到 200x300,Live2D 模型会跟着缩放,但点击命中的坐标映射会偏移。最简单的做法就是两边设成同一组数值。
3.4 编写 main.js:初始化、加载模型、注册错误提示
src/main.js是真正处理 live2D 加载逻辑的文件。用 Pixi 创建渲染应用,再把模型塞进渲染树。
import { Application } from 'pixi.js'; import { Live2DModel } from 'pixi-live2d-display'; async function init() { const app = new Application({ view: document.getElementById('live2d-canvas'), width: 300, height: 450, transparent: true }); try { const model = await Live2DModel.from('/models/haru/haru.model3.json'); model.scale.set(0.4, 0.4); model.anchor.set(0.5, 0.5); model.position.set(150, 220); app.stage.addChild(model); } catch (err) { console.error('模型加载失败,检查路径或跨域配置', err); } } init();逻辑说明:Application实例内部维护了渲染器、渲染树和帧循环。Live2DModel.from()负责请求并解析模型资源,返回一个可放入 Pixi 舞台的显示对象。addChild之后,帧循环开始每帧重绘模型,包含眼睛闭合、呼吸起伏、头发摆动等需要随时间更新的参数。
参数说明:scale.set(0.4, 0.4)把模型缩小到原始尺寸的 40%,anchor让模型中心对准后续的position坐标。这里position的 (150, 220) 是模型中心在 300x450 画布中的位置,不是左上角。这样设置后,角色会站在画布正中偏下,适合"半身看板娘"的构图。
3.5 启动、验证、收尾
npm run dev启动后访问终端输出的地址,页面右下角出现可以交互的角色。模型默认的自带动画会自动播放,比如眨眼、呼吸。
这里有个容易忽略的验证步骤:按下 F12 打开控制台,确认没有出现 404 报错,也没有打印模型加载失败的异常信息。如果模型没出现,优先看 Network 面板里 model3.json 的响应状态码,而不是先怀疑代码。第一步跑通之后,下一章的参数调优才有意义。
4. 模型动起来以后:缩放、锚点、事件交互与多模型管理
4.1 先调三个位置参数:缩放、锚点与坐标
模型加载后显示的尺寸是模型文件自带的默认大小,不同角色的画布尺寸差异很大,不改会导致角色超出画布或者缩成一团。加载完成后立刻设置缩放与锚点,是 demo 阶段的固定操作。
model.scale.set(0.35, 0.35); model.anchor.set(0.5, 0.5); model.position.set(150, 230);参数说明:scale的第一个值和第二个值分别是横向与纵向缩放比。Live2D 模型大多数情况下横纵同值缩放,但如果你把角色贴图做了非等比处理,可以分开设置矫正比例。anchor用 0 到 1 的小数表示锚点在模型自身坐标系中的位置,0.5 就是中心,锚点决定了 position 坐标对应模型的哪个点。position是锚点在 canvas 坐标系中的位置。这三个参数配合,能解决绝大部分"角色位置不对"的问题。
实际操作时,我会先把 scale 调到 0.3,然后根据角色头部是否完整露出微调 position。如果角色头钻进画布顶部,把 y 从 230 往下调到 250;如果角色半身被截断,把 y 往上提。这个调参过程没有公式,就是看画面微调,属于典型的"调到你满意为止"。
4.2 添加交互:点击动作、拖拽跟随与事件对象
pixi-live2d-display 的模型对象继承了 Pixi 的交互事件体系,能监听 click、pointerdown、pointerup 等事件,还能读取模型自身的命中区域集合。
model.on('pointerdown', (event) => { model.motion('tap_body'); model.focus(event.data.global.x, event.data.global.y); model.dragging = true; }); model.on('pointermove', (event) => { if (model.dragging) { model.position.set(event.data.global.x, event.data.global.y); } }); model.on('pointerup', () => { model.dragging = false; });这段代码做了三件事:按下时播放一个叫tap_body的动作,同时把模型锁定到跟随状态;移动时如果处于跟随状态,直接更新模型的 position;松开时解除跟随。这是"拖拽看板娘到任意位置"的常见实现。
参数说明:event.data.global是事件在画布上的全局坐标,可以直接拿来给 position 赋值。model.focus(x, y)会让人物的眼睛朝鼠标位置转动,是增强鲜活感的快捷接口。需要注意tap_body这个动作名并非每个模型都有,要先翻阅模型包下的motions/目录确认动作文件名,不然调用会静默失败。
另一个常用事件是hit,它接收一个命中区域的名称,用来实现"摸头、摸手有不同反应"。这个依赖模型美术在设计时设定的 hit areas,很多模型并没有做这个配置,所以用之前要确认模型文件里有没有对应的HitAreas字段。
4.3 多模型切换:从显示到释放的完整流程
一个页面只放一个角色太单薄,很多 demo 会加一个"换人"按钮,点击后随机切换模型。看似简单,里面藏着一个资源管理问题。
async function switchModel(path) { if (currentModel) { currentModel.destroy(); } app.stage.removeChildren(); const next = await Live2DModel.from(path); next.scale.set(0.35, 0.35); next.anchor.set(0.5, 0.5); next.position.set(150, 230); app.stage.addChild(next); currentModel = next; }代码要点:切换前先调用currentModel.destroy()释放旧模型的 GPU 纹理和 JavaScript 对象,然后清空舞台,再加载新模型。如果省略 destroy,新模型叠加旧模型,内存占用一步步上涨,移动端尤其明显。
这里还涉及一个路径管理:模型路径应该集中放在一个数组里,配合页面按钮或定时器轮播。每次切换都做请求和解析,加载期间页面不要重复触发切换按钮,否则会出现多个模型同时加载的竞争状态,界面表现就是角色跳来跳去。
多模型切换做到后面,你会发现模型的尺寸、位置、动作名不统一是常态。合理做法是给每个模型单独维护一套参数对象,切换时一并应用。把这些配置抽成一个 json,后续更换角色只需要加一条记录,不用改代码。
5. 集成 live2D demo 的避坑清单:五个真实翻车现场与排查路径
5.1 模型文件 404,页面只有透明 canvas
现象:页面正常打开,canvas 位置一片透明,控制台没有任何报错或只有一条资源加载失败的网络日志。
原因:路径写错是最常见的。模型实际在public/models/haru/haru.model3.json,代码里写的是/models/haru.model3.json,少了一层目录。相对路径的问题更多,比如写成./models/haru/haru.model3.json,页面路由一变化就失效。
解决:统一采用以/开头的绝对路径,并且确保模型文件确实放在项目的 public 目录下。遇到 404 时,打开 Network 面板直接看请求 URL 与实际文件路径的差异,这一步能解决大部分加载失败问题。
5.2 模型能加载但白屏或全透明
现象:Network 里所有资源都返回 200,控制台无错误,画布区域却什么都没有,或者整个画布被不透明的白底占据。
原因:白底的根源是把transparent配置成了false,或者 Pixi 初始化时没有给backgroundAlpha一个 0 值。而全透明的另一种可能是 WebGL 上下文在浏览器切后台后被销毁,回到页面时 canvas 已无法渲染。
解决:初始化 Application 时明确写transparent: true。上下文丢失的情况,在页面visibilitychange事件里监听状态变化,重建 Application 实例即可。这个坑在笔记本上频繁发生,属于标准的"黑匣子"问题——表面看不到报错,实际是 GPU 资源没恢复。
5.3 模型加载成功,但一动不动
现象:模型显示在页面上,没有眨眼、没有呼吸、没有任何动画。
原因:模型包可能被精简过,只保留了 moc3 和贴图,没有 motions 和 physics 配置。pixi-live2d-display 默认会播放 idle 动作,但 idle 动作本身来自模型文件,模型包里没有就无动画可播。
解决:检查模型文件夹里的 motions 目录。没有 motions 的情况下,可以在加载后手动调用model.motion('tap_body')测试动作系统;如果模型没有动作文件,只能换一个结构完整的模型包。拿模型时优先选官方示例或社区标注"完整版"的资源,可以省很多事。
5.4 点击角色没反应,但角色在正常动
现象:角色动画正常播放,但鼠标点上去没有任何交互反馈。
原因:常见的是 canvas 上方覆盖了其他透明元素。页面布局里如果有 nav、遮罩层或兄弟元素设置了定位,它们可能把 canvas 的点击事件吃掉。另一种原因是没有给 canvas 之外的容器设置pointer-events穿透。
解决:在浏览器 Elements 面板里选中 canvas,检查鼠标位置实际命中的元素是否被上层元素覆盖。把覆盖元素加上pointer-events: none,或者把 canvas 的z-index调到更高。这个现象在弹窗、导航栏多的大页面里特别容易出现。
5.5 移动端页面卡顿,内存持续上涨
现象:手机浏览器打开 demo,几秒后页面变卡,切动画时掉帧明显,进程内存一路往上走。
原因:移动端 GPU 资源有限。常见诱因有三个:一是用了超大尺寸贴图,比如原始纹理是 4096x4096,手机每一帧都要对它做纹理采样;二是频繁切换模型没有正确释放旧实例;三是开了多个 Application 实例或者全局没有限制物理计算参数。
解决:移动端测试时先把画布尺寸调小,贴图如果能压缩就压缩到 2048 以下。切换模型时严格按"先 destroy 再加载"的顺序执行。整个页面只保留一个 Application 实例,不要在组件生命周期里反复创建。真机上如果还卡,把 physics.json 里的参数数量砍掉一半,肉眼基本感知不到差别,帧率能回升不少。
如果你同时遇到多个现象,我的排查顺序是:先看 Network 确认资源全部加载,再看 console 确认无 JS 异常,然后检查画布是否被遮挡,最后才怀疑渲染性能和 WebGL 问题。按这个顺序能少走很多弯路。
6. 从 demo 到可用:性能验证与模型替换的通用流程
6.1 用 Performance 面板验证渲染性能的三步操作
第一步:打开开发者工具的 Performance 面板并点击录制。第二步:在页面上进行典型的用户操作,比如拖拽角色、点击切换表情、滚动页面。第三步:停止录制,观察 FPS 通道与 Memory 曲线。重点看两个阈值:静止时是否保持 50fps 以上,动作播放时是否低于 30fps。若低于,优先砍 physics 参数,如果砍了还不行再换低分辨率贴图。Memory 曲线只涨不跌,说明模型实例没释放,回代码里补 destroy 调用。
这一套观测动作我每次替换模型都会做,养成了肌肉记忆。原本只是"能跑"的 demo,经过这轮验证才能真正长久挂在你页面上而不被用户投诉卡顿。
6.2 模型替换与验收清单
换模型是 demo 期最频繁的操作。我有一套固定检查顺序:
| 检查项 | 通过标准 |
|---|---|
| model3.json 路径 | Network 请求返回 200 |
| Cubism 版本 | 3 或 4,拒绝旧格式 |
| 贴图分辨率 | 长边不超过 2048 |
| motions / physics 存在 | 目录里有对应 json |
| 交互动作名 | 与模型 motions 目录对应 |
每次换模型按这个表过一遍,五分钟完事。这套清单是从几次翻车里提炼出来的,印象最深的一次是模型本地预览正常,部署到服务器后白屏,排查一晚上才发现是服务器没配 .moc3 的 MIME 类型,返回了 text/plain。你最好也把这一条加进清单里。
从那以后我养成了一个习惯:验证新模型前先把清单打开,不放过任何一行。这个习惯帮我抵掉了不少返工时间。希望帮到你。
本文还有配套的精品资源,点击获取