
简介面向Web前端、游戏与互动娱乐站点的开发者这是一份演示HTML页面集成Live2D的完整Demo项目重点解决“如何在网页里加载二维角色模型并响应用户操作”的常见问题。压缩包共612个文件大小17.29MB其中307个mtn动作文件、158个json配置、51个png贴图、44个mp3语音、22个moc模型是核心素材另有2个html页面、2个js脚本和1个md说明文档覆盖从模型数据到交互代码的完整链路便于逐步拆解学习。已有1433人学习下载。示例实现了Live2D库引入、Canvas画布绑定、模型初始化、动态更换人物、触摸事件监听以及按钮等页面组件与角色的联动反馈从初始化到交互都有可直接运行的完整代码。借助这些内容读者既能理解Cubism模型的加载与渲染原理也能将素材和逻辑抽取到自己的网站中复用节省从零搭建和调试时间。1. HTML 集成 Live2D 的 demo 没那么玄先跑通再谈定制HTML 页面里放一个会眨眼、能跟着鼠标转头、点击还有小反馈的 Live2D 看板娘早已是个人博客和产品 demo 的常见装饰。第一次搜“html 集成 live2D demo”的人大多不是想啃官方 SDK 文档而是想拿到一份能直接打开、能看到模型动起来的示例代码。这篇文章就按这个诉求展开先讲清楚模型文件与 SDK 怎么选再给出 npm 构建和纯 CDN 两种可复现的最小 demo最后把路径、跨域、交互这些高频坑挨个过一遍。无论你是静态页还是 Vue/React 项目这套思路都能复用。2. 选模型还是选 SDKLive2D 集成前先搞懂三件事2.1 模型文件的血统.moc 与 .moc3 决定你后续用什么库Live2D 模型不是一张 PNG而是一套“骨骼、网格、纹理、动作”的组合文件。Cubism 2.1 时代的裸模型以.moc文件为核心配套model.json描述文件Cubism 3.0 之后是.moc3文件配套model3.json。这两代格式不互通SDK 选错直接加载失败根本不会渲染任何东西。一个 Cubism 3/4 模型的典型目录结构如下models/ └── shizuku/ ├── shizuku.model3.json ├── shizuku.moc3 ├── shizuku.physics3.json ├── shizuku.cdi3.json ├── textures/ │ ├── texture_00.png │ └── texture_01.png ├── motions/ │ ├── idle.motion3.json │ └── tap.motion3.json └── expressions/ └── happy.exp3.jsonmodel3.json是整个模型的入口描述文件里面记录了 moc3 文件路径、纹理列表、物理模拟参数、动作列表和表情列表。浏览器加载时SDK 通过解析这个 JSON 依次去拉取其它资源。如果入口 JSON 里写的是相对路径那么模型目录一旦整体移动路径仍然有效如果手动改过里面的路径字段目录结构就对不上了。Cubism 2.1 的模型与之类似但入口是model.json动作文件后缀是.motion.json。两种格式不能混用。市面上不少开源示例仓库同时提供两代模型但如果你是拿别人 demo 改的先看清楚入口文件是model3.json还是model.json这决定了后面加载代码里Live2DModel.from()的参数。顺带回答一个热门问题“有了 AI以后是不是不用 Live2D 了” 至少现阶段不是。AI 生成的是静态角色形象或语音Live2D 负责让这张脸在网页里实时驱动、随鼠标反馈。两者定位不同Live2D 的实时交互能力反而是它的护城河。2.2 三条集成路线官方 SDK、pixi-live2d-display、纯 CDN集成 Live2D 到网页主流做法是这三条路。第一条是官方 Cubism SDK for Web。它功能最全、和 Cubism Editor 保持同步但需要你手动引入 Cubism 核心运行时和 framework 脚本目录结构也比较绕。如果只是给个人站加个看板娘这种工作量有点大。第二条是社区库pixi-live2d-display基于 PixiJS 封装。它把 Live2D 模型包装成 PIXI 的显示对象加载一行、定位一行、加入舞台一行几乎零心智负担。这也是现在个人站点集成 Live2D 最常见的选择。第三条是纯 CDN 方式不装 Node、不用打包器用script标签直接把 pixi.js 和 pixi-live2d-display 引进来配合几十行 JS 在静态页面里跑起模型。这通常就是搜 “html 集成 live2D demo” 的人最想要的做法。三条路线的对比用一张表说清楚最直接路线适用场景优点缺点官方 Cubism SDK深度定制、要最新特性功能全、与 Cubism Editor 同步引入复杂、demo 结构重pixi-live2d-display npmVue/React 工程化项目API 简单、生态成熟依赖 PixiJS 版本纯 CDN 方式静态页、临时 demo零构建、开箱即用不好做深层定制选型可以用一个简单决策来判断如果项目本身用 Vue/React 打包器走 npm pixi-live2d-display如果只是给一个纯静态 HTML 加个模型走 CDN 方式不值得为一页 demo 引入整套构建链。2.3 模型资源去哪找示例仓库、免费下载与版权风险模型不是从 SDK 里自动生成的。最常见来源有三个。一是官方示例模型。Cubism SDK 包里自带几个演示模型社区里大量 demo 用的也是官方放出的示例资源。二是 GitHub 上的 live2d 模型资源仓库搜索 “live2d 模型资源” 或 “live2d downloads” 能找到不少下载时确认入口文件是model3.json还是model.json。三是游戏提取资源搜“live2d下载免费”“碧蓝航线live2d播放器”这类关键词能找到很多二次元手游提取文件。这里说一个我自己的习惯游戏提取模型只做本机学习 demo 可以公开发布或商用基本都有版权风险。真的要做站点的话优先用官方示例模型保底没有纠纷。拿到模型之后不要急着写代码先打开model3.json看三个字段FileReferences.Moc、FileReferences.Textures、FileReferences.Motions。确认里面的路径是相对路径且与实际目录匹配再落到集成代码上。这一步能省掉后面大半的 404 排查时间。3. 用 pixi-live2d-display 跑通最小 live2D demonpm 构建全流程3.1 项目初始化与依赖安装推荐用 Vite 而不是 Webpack。理由很简单配置少、启动快适合 demo 快速验证。命令行如下npm create vitelatest live2d-demo -- --template vanilla cd live2d-demo npm install npm install pixi.js7 pixi-live2d-display这里pixi.js是渲染引擎负责 WebGL 绘制pixi-live2d-display是适配层把 Live2D 模型变成 PIXI 可渲染对象。两个库的版本需要配套pixi-live2d-display 目前主要兼容到 PixiJS 7.x直接写pixi.js7可以避开 v8 的 API 差异。如果你先用默认的npm install pixi.js装到了 8.x加载模型时大概率会报一些底层对象的构建错误那不是代码问题是版本不匹配。还要把模型文件放到项目里。Vite 项目约定放在public/models/下的文件会被原样拷到打包产物根目录并且路径写法不加public/前缀。放好之后目录长这样live2d-demo/ ├── index.html ├── main.js └── public/ └── models/ └── shizuku/ ├── shizuku.model3.json ├── shizuku.moc3 └── textures/这一步有一半的新手会在这里卡住模型文件放到了src/models下然后发现Live2DModel.from()根本找不到资源。原因很简单src目录是要被构建工具做依赖分析的而模型文件是一堆 JSON、PNG、二进制的混合体不属于模块代码放进public才是让浏览器直接访问它们的最优方式。3.2 加载模型并写入页面核心代码逐行拆解把main.js改成下面这样import * as PIXI from pixi.js; import { Live2DModel } from pixi-live2d-display; // 创建 PIXI 应用实例绑定到页面上的 canvas const app new PIXI.Application({ view: document.getElementById(canvas), autoStart: true, resizeTo: window, backgroundAlpha: 0, // 背景透明让模型叠在页面上 }); async function init() { // 加载模型入口是 model3.json 的相对路径 const model await Live2DModel.from(/models/shizuku/shizuku.model3.json); // 加入 PIXI 舞台 app.stage.addChild(model); // 锚点居中按模型中心定位 model.anchor.set(0.5, 0.5); model.scale.set(0.25); // 放到视口右下角 model.position.set(app.screen.width - 100, app.screen.height - 120); // 自动交互与自动更新 model.autoInteract true; model.autoUpdate true; } init();对应index.html只需要一个 canvas 容器!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleHTML 集成 Live2D Demo/title style canvas { position: fixed; right: 0; bottom: 0; z-index: 999; pointer-events: none; /* 让看板娘不遮挡页面操作 */ } /style /head body canvas idcanvas/canvas /body /html逻辑说明Live2DModel.from()是异步方法内部先拉取 model3.json解析出纹理、动作、表情列表后依次加载全部就绪才返回模型实例。所以后面直接addChild是安全的。app.screen.width在resizeTo: window后就是视口宽度用它做右侧定位不需要手动监听 resize。anchor.set(0.5, 0.5)之后position表示模型中心点模型缩放时不会跑偏。有一个容易忽略的细节我给 canvas 加了pointer-events: none这样模型不会挡住页面里其它按钮的点击。如果你打算让模型响应鼠标和触摸得去掉这行并把 canvas 尺寸限制在右下角一小块区域不要把整个视口都变成交互层。3.3 必调参数缩放、位置、锚点与自动交互模型出来之后反复调位置是很正常的事。常用参数就这几个参数作用备注model.scale.set(s)整体缩放0.15 ~ 0.3 之间常用model.anchor.set(ax, ay)锚点0.5, 0.5 便于按中心定位model.position.set(x, y)位置用 app.screen 做右对齐model.autoInteract自动交互true 时点击/触摸有反馈model.autoUpdate自动更新false 时模型不呼吸不眨眼model.rotation偏转角度单位是弧度小角度可用scale和position的配合有个技巧先设 scale 再设 position或先设 position 再设 scale最终渲染位置是不同的。因为position是锚点坐标scale 改变的是显示尺寸二者相互作用。建议固定顺序为先 scale 再 position减少心智负担。调试时可以临时把backgroundAlpha: 1在 canvas 里看到模型的实际渲染边界确认模型有没有超出画布。如果模型贴边显示不全多半是锚点和位置没配合好而不是模型本身有裁切问题。等位置调好再改回backgroundAlpha: 0。4. 静态页救星CDN 方式把 Live2D 直接集成进 index.html4.1 为什么 CDN 方式也能跑通script 标签顺序是硬约束不装 Node、不用打包只把两个 script 标签贴进 HTMLLive2D 模型就能在静态页里跑起来。很多第一次接触的人觉得这是玄学其实原理很简单pixi-live2d-display 在初始化时会找全局对象window.PIXI所以必须先加载 pixi.js再加载适配层。script srchttps://cdn.jsdelivr.net/npm/pixi.js7/dist/browser/pixi.min.js/script script srchttps://cdn.jsdelivr.net/npm/pixi-live2d-display/dist/index.min.js/script这两个 script 如果放在head里浏览器发现它们是同步脚本会阻塞渲染直到加载完成。对于 demo 页面来说这不是问题如果你想优化首屏可以把它们移到body末尾反正模型本来就是在 DOM 就绪后再创建的。有一点要提醒CDN 方式默认拉的是最新版如果哪天适配层升级后 API 变了你的页面可能直接报错。稳妥做法是把版本号固定下来比如pixi.js7.4.2升级时自己去测。Demo 阶段不锁版本问题不大真上生产必须锁。4.2 一个能直接打开的完整 HTML demo下面这份代码复制到任意目录配合一个合法的模型文件路径浏览器打开就能看见模型!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleHTML 集成 Live2D Demo/title style body { margin: 0; overflow: hidden; background: #222; } canvas#live2d-canvas { position: fixed; right: 0; bottom: 0; z-index: 10; pointer-events: none; } /style /head body canvas idlive2d-canvas/canvas script srchttps://cdn.jsdelivr.net/npm/pixi.js7/dist/browser/pixi.min.js/script script srchttps://cdn.jsdelivr.net/npm/pixi-live2d-display/dist/index.min.js/script script window.addEventListener(DOMContentLoaded, () { const app new PIXI.Application({ view: document.getElementById(live2d-canvas), autoStart: true, resizeTo: window, backgroundAlpha: 0, }); PIXI.live2d.Live2DModel.from(./models/shizuku/shizuku.model3.json) .then(model { app.stage.addChild(model); model.anchor.set(0.5, 0.5); model.scale.set(0.25); model.position.set(window.innerWidth - 100, window.innerHeight - 120); }) .catch(err console.error(模型加载失败:, err)); }); /script /body /html这里的关键是PIXI.live2d.Live2DModel这个全局对象。npm 版本里你是import { Live2DModel }CDN 版本里它挂在PIXI.live2d命名空间下本质是同一个类。如果直接写Live2DModel.from()而不带PIXI.live2d前缀在 CDN 模式下会报未定义。如果把pointer-events: none去掉模型就能接收鼠标事件但要注意它可能挡住下层页面的按钮。常见折衷做法是把 canvas 尺寸限定在右下角固定像素范围内只让那一小块区域响应事件。4.3 文件和目录的最佳组织方式CDN 模式下目录建议这样放live2d-page/ ├── index.html └── models/ └── shizuku/ ├── shizuku.model3.json ├── shizuku.moc3 ├── shizuku.physics3.json ├── textures/ │ ├── texture_00.png │ └── texture_01.png └── motions/ └── idle.motion3.json页面里用相对路径./models/shizuku/shizuku.model3.json指向入口文件。模型内部 textures/motions 的路径是相对于 model3.json 所在目录的所以整个models/文件夹原样拷贝不要手动改动里面的路径字段。高频翻车点有两个。一是文件名大小写Linux 服务器上文件名区分大小写本地 Windows 不区分所以在本地跑通后上传到服务器经常 404。二是不要把多个模型的目录并到同一个父目录下而不区分如果两个模型都有texture_00.png目录层次不清会互相覆盖。我的习惯是每个模型单独一个子目录目录名跟入口文件名保持一致这样排查问题时一眼能对上。5. 避坑指南Live2D 集成最常见的 5 个翻车现场5.1 模型一直 404控制台刷屏找不到 model3.json现象Network 面板里 model3.json 请求失败页面空白。原因最常见的是路径问题。入口文件用了绝对路径/models/...但站点部署在子目录下或者文件名大小写不一致再或者模型目录根本没有放进部署目录。解决先看 Network 里实际请求的 URL 和磁盘目录对上没有。把路径改成相对路径./models/xxx.model3.json或按部署环境配置base路径。大小写问题在本地 Windows 开发时很难发现部署到 Linux 服务器后才会暴露所以第一次上线前专门检查一遍文件名大小写。5.2 纹理加载出来了但模型是白模现象模型有轮廓但纹理缺失像半透明的塑料块。原因跨域。双击 HTML 用file://协议打开时canvas 加载本地纹理会被浏览器判定为跨域资源WebGL 无法上传纹理数据。解决不要直接双击 HTML在项目目录起一个静态服务python -m http.server 8000然后访问http://localhost:8000。如果模型文件放在远程 CDN需要给静态服务器配Access-Control-Allow-Origin: *响应头。这个坑属于血泪经验任何 WebGL 相关项目都会遇到不只是 Live2D。5.3 模型出来了但纹丝不动像个贴图现象页面渲染出了模型但既不眨眼也不呼吸鼠标移动没反应。原因autoUpdate或autoInteract被改成 false或者 PIXI 应用没有开启 Ticker还有一个少见情况——模型本身没有待机动作。解决把这两行显式写出来model.autoUpdate true; model.autoInteract true;如果还不动检查创建应用时有没有误设autoStart: false。再不动用浏览器 Console 手动执行model.autoUpdate true如果模型立刻动起来说明参数问题还没动就去model3.json里检查 motions 列表是否真的有 idle 类动作。5.4 npm 构建打包后模型路径 404现象本地开发时模型显示正常npm run build后部署到线上就找不到模型资源。原因Vite/Webpack 的产物目录里没有模型文件或者路径前缀不对。模型文件放在了src而不是public打包时没有被处理或者部署到子路径时/models匹配不到。解决Vite 项目里模型统一放public/models/代码中路径写作/models/xxx.model3.json。如果是子目录部署给vite.config.js设置base: ./同时代码里改用相对路径./models/...。不要用 import 语句直接引入 model3.json它不是 JS 模块构建工具不会帮你处理里面的纹理引用。5.5 桌面端正常手机端触摸完全没反应现象桌面浏览器鼠标可以点击但手机触摸模型没有反馈。原因PIXI 的交互默认处理鼠标事件移动端触摸坐标映射依赖 viewport 和 canvas 尺寸。如果 canvas 被 CSS 强制缩放触摸坐标跟 WebGL 内部坐标对不上或者忘了加 viewport meta 标签。解决head 里加meta nameviewport contentwidthdevice-width, initial-scale1.0canvas 尺寸不要用 CSS 强制缩放让resizeTo: window去决定。用 Chrome DevTools 设备模拟器勾选 “Emulate touch events” 复现并验证。6. 进阶玩法点击反馈、多模型切换与框架集成6.1 点击模型触发动作pixi-live2d-display 把模型内部动作统一封装成了model.motion()方法。点击模型播放 “tap” 动作是最常用的交互写法model.interactive true; model.hitArea new PIXI.Rectangle(0, 0, model.width, model.height); model.on(pointerdown, () { model.motion(tap); });这里hitArea必须设置否则 PIXI 不知道模型的点击区域。动作返回 Promise如果要精确串行播放可以在.then()里接下一段逻辑。6.2 多模型切换多模型切换最稳妥的思路是“先卸载再加载”不重启整个 PIXI 应用async function switchModel(url) { if (currentModel) { app.stage.removeChild(currentModel); currentModel.destroy(); } currentModel await Live2DModel.from(url); app.stage.addChild(currentModel); currentModel.anchor.set(0.5, 0.5); currentModel.scale.set(0.25); currentModel.position.set(window.innerWidth - 100, window.innerHeight - 120); }注意先removeChild再destroy顺序反了有时会残留引用。切换期间的异步加载建议加个 loading 占位避免用户连点导致并发加载。6.3 Vue/React 框架集成的注意点框架里集成本质是把 PIXI 应用的生命周期绑定到组件生命周期。以 Vue 3 为例onMounted里创建应用onUnmounted里销毁避免组件卸载后 canvas 仍然占用 WebGL 上下文。另外路由切换时模型会跟着整个页面重新挂载如果页面有侧边栏或底栏不要写死resizeTo: window。监听布局容器尺寸动态给model.position重新赋值才能避免模型盖住导航。我在自己的博客里用的是 CDN 方案的变体平时也维护一个多模型切换的组件。如果你从零开始先用第 4 章的完整 demo 把模型跑出“会动”的感觉再加点击和切换。等真需要给高流量的页面做产品化再考虑组件封装和 Canvas 复用的性能优化。希望帮你把这条路的坑提前填平。本文还有配套的精品资源点击获取