HTML集成Live2D看板娘:从模型原理到可拖拽交互的实践
简介HTML集成Live2D的demo压缩包面向希望为网页添加动态二次元角色交互的Web前端开发者、游戏与娱乐站点设计师以及刚接触Live2D的初学者。资源以一套可直接运行的示例工程展示如何在HTML页面中引入Live2D库、配置JSON模型、创建Canvas画布并完成初始化同时涵盖模型动态切换、触摸事件响应以及与按钮等页面组件联动等常见需求。压缩包共612个文件容量约17.29MB其中包含大量mtn动作文件、json配置文件、moc模型文件、png贴图与mp3音频并辅以html示例页和js脚本便于对照代码拆解实现逻辑。目前已有1431人学习下载适合作为实战入门参考。整包目录结构清晰文件类型覆盖Live2D开发的主要环节开发者解压后可直接运行示例也可提取所需模型与动作资源嵌入自己的项目。1. HTML 里集成 Live2D 看板娘二十行代码就能跑通如果你敲的是「html集成live2D,demo」这组词大概率不是想研究 Live2D 的建模原理而是正对着自己那个还只有蓝色链接的 HTML 页面发愁怎么才能加一个会眨眼、会呼吸、能拖拽的二次元角色让页面看起来不再是纯文档。这个需求在 web 前端里已经非常成熟——PixiJS 负责渲染pixi-live2d-display 社区库把 Live2D 模型封装成一个普通显示对象你只需要把模型文件按规范放进项目目录十几行脚本就能让看板娘站到页面角落。整个过程不需要碰官方 Cubism SDK 的底层初始化不需要写一行 WebGL 着色器demo 用不了半小时就能传给别人看。这篇笔记按我实际跑通过的方案写把模型选型、最小 demo、参数调节和踩过的坑一次说清楚适合个人博客、官网角色展示也适合前端新人把「会动的角色」当成第一个网页组件练手。2. 集成前先搞懂 Live2D 模型资源文件与运行原理2.1 模型文件拆解.moc、贴图、配置 json 各自管什么Live2D 和传统 3D 模型最大的区别是它没有立体网格本质是「一张可拆分的 2D 插画 一套变形网格 一组驱动参数」。运行时浏览器用 WebGL 把拆开的贴图零件按参数逐帧变形、叠加、渲染。这套东西落到文件层面通常由下面几类文件组成。第一个是骨骼动作数据Cubism 2 时代是 .mocCubism 3/4 时代是 .moc3。这个二进制文件记录模型的顶点索引、变形器参数和骨骼层级它不关心角色长什么样只负责「哪里能动、怎么动」。同一个 .moc3 换上不同贴图就能出换色版本说明骨骼和外观是解耦的。第二个是贴图纹理。Live2D 模型最少一到两张 PNG常见尺寸是 2048×2048。建模师通常按身体、头发、脸、配件拆成多张图集导出运行时按 json 里的坐标裁剪后贴到变形网格上。你可以把 .moc3 理解成关节和骨架PNG 只是附在骨架外面的皮肤。换贴图、做换色都是在这一层动手脚。第三个是配置 json。Cubism 2 模型的入口文件是模型名.model.jsonCubism 4 是模型名.model3.json。它相当于模型的「简历」Moc 字段指向 .moc3 文件Textures 数组列出全部贴图Motions 字段按动作组罗列动作脚本Physics 字段指向物理配置文件。Live2D 加载器先读这个 json再按里面写的相对路径逐个申请资源所以 json 一旦和实际目录对不上模型就出不来。最后是几个可选增强文件。.motion3.jsonCubism 2 是 .motion.json定义关键帧动画负责眨眼、摆手、转头.physics3.json 算头发、裙摆、配饰的物理摆动.exp3.json 定义表情换装。一个精细模型可能带几十个动作和表情文件但它们不是运行必需缺了 json 对应声明也不影响角色渲染。看到这里应该能明白一件事所谓集成 Live2D本质上就两件事。第一让页面能加载并渲染模型文件第二保证模型文件之间的相对路径完整。后面遇到的所有坑几乎都绕不开这两点。2.2 模型资源从哪来官方示例、社区免费模型、解包资源与自制想跑通 demo第一步是搞到一个模型。我按靠谱程度排个序。官方示例模型最适合入门。Live2D 官网提供一批可直接下载的角色像 Shizuku、Hiyori、Haru、Mao、Rice 这些质量稳定、目录干净多数下载包已经按 model3.json moc3 贴图规范组织好拿下来就能加载。这类资源适合技术验证和 demo但授权通常只覆盖学习和评估别直接往商业产品里塞这个写在官网每个模型的下载页里了动手前值得花一分钟看一遍。社区开源项目的附带模型也常见。GitHub 上不少「博客看板娘」类项目会在仓库里内置几套免费模型跟着项目目录拿一份出来就能用。这类模型的来源比较杂有的出自官方示例有的是爱好者自建授权要看各自仓库说明。单纯学习 demo 基本没问题但复制到商业项目之前得把授权链弄清楚。游戏解包资源则要谨慎。碧蓝航线等手游的 live2d 解包资源在很多交流群里流转质量高、动作全放进 demo 里跑通很容易。但这类资源有两个风险一是游戏用户协议普遍禁止拆解和再分发二是解包文件往往和原始目录结构强绑定漏一张贴图就是花屏。我的建议是只在本地学习用不丢上生产环境更不要拿去给客户做演示。本篇文章不展开讨论解包工具也不建议花时间在这条路上钻研。自制模型是另一条路。用 Live2D Cubism Editor 从原画分件、建网格、绑骨骼到导出一个完整角色以天为单位计算但这是授权最干净、可定制性最高的方向。如果项目就是打算长期用同一个角色的形象自制或购买授权模型是唯一能落地的选择。想快速跑 demo 的话这个选项可以往后放。市面上还有各种模型站下载的免费资源质量参差不齐有的把 json 路径改得乱七八糟下载下来还得自己改配置。demo 阶段我不建议在资源站上耗时间先用官方示例把流程跑通把「加载-渲染-交互」这条链路吃透比囤一堆花屏模型有用得多。2.3 一个能跑的模型目录长什么样文件清单与目录规范以我本地常用的一个 Cubism 4 模型为例目录结构是这样shizuku/ ├── shizuku.model3.json # 入口配置 ├── shizuku.moc3 # 骨骼数据 ├── shizuku.physics3.json # 物理配置可选 ├── shizuku.exp3.json # 表情配置可选 ├── texture_00.png # 贴图可能有多张 ├── texture_01.png └── motions/ # 动作脚本目录可选 ├── idle_01.motion3.json ├── tap_body.motion3.json对应的 Cubism 2 老模型目录则长这样shizuku/ ├── shizuku.model.json # 入口注意后缀没有 3 ├── shizuku.moc ├── textures/ │ ├── texture_00.png │ └── texture_01.png └── motions/ └── tap_body.motion.json两代模型的目录差异集中在三处入口 json 分别是 model.json 和 model3.json骨骼文件是 .moc 和 .moc3动作脚本的大小写习惯不同Cubism 2 里 motions 全小写Cubism 4 里 Motions 首字母大写。加载器靠这些特征区分版本所以拿到模型第一件事就是看入口文件后缀别急着改写。目录规范上我的建议是一个角色一个目录目录名不用中文和空格贴图统一放 textures 子目录或者保持 json 原样动作、表情、物理各自建子目录。因为这些 json 里的路径都是相对自身位置计算的整个目录从 A 项目复制到 B 项目时可以整体搬走拆开就全断了。每拿到一个新模型我习惯先用本地静态服务加载一遍确认能出人再往业务代码里接这一步能提前过滤掉大部分资源问题。3. 跑通最小 demo用 pixi-live2d-display 把看板娘挂到 HTML3.1 选型对比为什么用 pixi-live2d-display 而不是官方 SDK把 Live2D 集成进浏览器的方案有三条路我实际用过两条第三条也调研过。第一条是官方 Cubism SDK for Web。它直接暴露 Cubism Core 的底层 API开发者要手动创建 Runtime、绑定模型、逐帧调用 update 并把渲染结果交给 WebGL 上下文。功能最全但太重了。早年为了在项目里用一个老 .moc 模型光初始化渲染上下文就折腾了一晚上最后发现多数时间是在重复造轮子。第二条是 pixi-live2d-display。它把 Live2D 模型封装成一个 PixiJS 显示对象加载、贴图、更新、点击命中全部处理好了。你在 PixiJS 舞台上把它当成一个普通精灵来管设 scale、position、挂交互事件其余交给库。它同时支持 Cubism 2 和 Cubism 4 两代模型入口脚本分开兼容性做得很省心。第三条是 live2d-widget 这类博客挂件。整套脚本塞进页面自带默认模型和配置页零代码部署。适合博客、个人主页这种只要好看就行的场景但定制性太差想在项目里精确控制位置、动作、层级反而很别扭。三个方案我整理成一张表方案上手成本定制性适合场景官方 Cubism SDK for Web高需理解渲染循环与 Runtime 管理最高深度定制、原生 WebGL 项目pixi-live2d-display低半天能跑通高组件化接入普通业务页面接入看板娘live2d-widget 挂件最低复制即用低功能受限博客、个人主页装饰结论很直接页面本身不是以 Live2D 为核心而是想「加一个角色」到既有页面里pixi-live2d-display 是最平衡的选择。下面所有 demo 都按这个方案写。3.2 最小 demo一个 HTML 文件把看板娘跑起来这里我用 CDN 方式写不搭构建工具拿到模型后起个本地服务就能验证。完整代码先贴出来后面逐段解释。!DOCTYPE html html langzh-cn head meta charsetutf-8 titleLive2D Demo/title style #live2d-canvas { position: fixed; right: 0; bottom: 0; width: 300px; height: 360px; z-index: 10; } /style /head body canvas idlive2d-canvas/canvas !-- 1. Cubism Core 运行时提供底层渲染能力 -- script srchttps://cdn.jsdelivr.net/npm/live2dcubismcorelatest/live2dcubismcore.min.js/script !-- 2. PixiJS 渲染引擎 -- script srchttps://cdn.jsdelivr.net/npm/pixi.js6/dist/browser/pixi.min.js/script !-- 3. pixi-live2d-display 的 Cubism 4 版本入口 -- script srchttps://cdn.jsdelivr.net/npm/pixi-live2d-display0.4.0/dist/cubism4.min.js/script script (async function () { // 创建 PixiJS 应用实例直接渲染到目标 canvas const app new PIXI.Application({ view: document.getElementById(live2d-canvas), width: 300, // 逻辑宽度和 CSS 保持一致 height: 360, // 逻辑高度 autoStart: true, transparent: true, // 老版本 PixiJS 的透明写法 backgroundAlpha: 0, // 新版本 PixiJS 的透明写法 autoDensity: true, // CSS 尺寸与物理像素自动匹配 resolution: 1 // 普通屏 1高分屏可调 2 }); // 异步加载模型配置返回 Live2D 显示对象 const model await Live2DModel.from( ./shizuku/shizuku.model3.json, { autoInteract: true } // 自动响应鼠标点击 ); // 挂到舞台 app.stage.addChild(model); // 缩放与位置数值按模型实际大小调 model.scale.set(0.18, 0.18); model.x 0; model.y 0; })(); /script /body /html逻辑说明三个 script 的顺序是硬约束。live2dcubismcore.min.js 提供 Live2DCubismCore 全局对象pixi-live2d-display 内部直接引用它完成 WebGL 渲染PixiJS 提供舞台和渲染循环最后加载的 cubism4.min.js 注册 Live2DModel 类。顺序反了会直接报 Live2DCubismCore is undefined。注意Live2DModel.from()是异步方法返回 Promise必须 await 拿实例。常见错误是写成同步调用后面 addChild 时 model 还是 undefined控制台抛 TypeError。这个错误几乎每个第一次接触这个库的人都会遇到属于入门必经坎。参数说明里重点看三个transparent和backgroundAlpha一起设 0是为了兼容不同版本的 PixiJS只要少写一个某些版本下 canvas 就会露出白底resolution在 Retina 屏建议设 2否则模型边缘发虚代价是 GPU 负载翻倍scale没有统一公式因为每个模型原始画布大小不同我的习惯是先设 0.2 看效果再按角色实际大小微调。如果你的模型是 Cubism 2 老格式入口是 .model.json、内部引用 .moc 文件就把第三个 script 换成dist/cubism2.min.js其余代码几乎不用动。两种格式的加载器入口是分开的混用会导致加载失败后面避坑章节会展开说。3.3 动作与命中交互让看板娘对点击有反应pixi-live2d-display 把模型可交互区域抽象成 hit 事件。模型 json 里会声明若干 HitArea常见的有 Body、Head。你监听 hit 事件拿到本次点击命中的所有区域名数组再决定播放哪组动作。// 在模型成功加载后绑定 model.on(hit, (hitAreas) { // hitAreas 是本次点击命中的区域名数组 if (hitAreas.includes(Head)) { model.motion(TapHead); // 播放头部动作组 } else if (hitAreas.includes(Body)) { model.motion(TapBody); // 播放身体动作组 } });motion 方法的第一个参数是动作组名不是具体文件名。动作组在 model3.json 的 Motions 字段里定义值是数组。实际使用时要先用编辑器打开模型 json确认组名是 Idle、TapBody 还是自定义的名字写错不会报错只会没反应排查起来特像玄学。这里有个容易踩的坑模型 json 里没有声明 HitArea 时hit 事件永远返回空数组。官方示例模型基本都有第三方解包模型就不一定。解决办法是手动往 model3.json 里补一段 HitAreas 定义或者干脆只用 pointertap 事件做全局点击判断。后者实现最简单但没法区分点到头还是点到手。autoInteract这个参数管的是更底层的交互行为设为 true 时库会自动处理鼠标按下、抬起的默认响应保证模型触碰表现是「活」的。demo 阶段保持 true 最省心等做精细定制再按需关掉避免事件链路冲突。3.4 多个模型并存与切换加载两个角色或换人有的项目希望页面里同时出现两个角色或者做一个切换角色按钮。pixi-live2d-display 对多模型支持很自然每个 Live2DModel 就是舞台上的一个普通节点重复调用 Live2DModel.from 即可。// 同时加载两个角色 const modelA await Live2DModel.from(./shizuku/shizuku.model3.json); const modelB await Live2DModel.from(./haru/haru.model3.json); modelA.scale.set(0.15); modelB.scale.set(0.15); modelA.x 0; modelB.x 200; // 并排摆放 app.stage.addChild(modelA, modelB);注意事项三个第一两个模型的 scale 要分别调原始画布高度不一样直接 addChild 可能一个大一个小。第二内存占用是叠加的每多一个模型GPU 上就多几张贴图和一份骨骼数据约束低端机上两个模型同时跑会明显掉帧。第三切换角色时记得调用model.destroy()释放资源否则页面反复切换会让内存只增不减这是很多 demo 里最容易被忽略的一点。用 Vite 或 Webpack 做工程化时pixi-live2d-display 走 npm 安装import 方式略有区别import { Live2DModel } from pixi-live2d-display还要在入口处确保 Live2DCubismCore 全局可用。demo 写成 CDN 是因为零构建适合直接照抄。4. 集成到现有页面后的常见问题定位与避坑清单4.1 双击 HTML 直接黑屏先查跨域与静态服务现象本地目录结构完全正确模型文件也在但双击 index.html 打开Live2D 区域一片空白控制台清一色报 CORS policy 错误或者直接显示 Failed to load resource。原因浏览器不允许 file:// 协议下的页面通过 fetch 读取本地 json 和 PNG 资源。Live2D 模型加载依赖 fetch 读取配置、图片、动作文件所以安全策略直接把整个加载流程掐断了。这不是代码问题是运行方式问题很多人第一次跑通 demo 时都在这上面卡住。解决本地起一个 HTTP 服务Python 自带一行命令。python -m http.server 8080然后浏览器访问 http://localhost:8080 打开你的页面。Vite 用户更简单项目根目录跑 npm run dev把模型目录放到 public 或 static 下直接用。我的习惯是任何时候都用本地服务验证 demo从不用 file:// 双击这个习惯帮我省掉了大量「代码没动但莫名黑屏」的排查时间。4.2 模型贴图花屏或身体缺失路径不一致与纹理尺寸现象模型出来了但身体是花的有的部位发黑有的部位是一块块的色片。打开 DevTools 的 Network 面板能看到某张 PNG 请求返回 404。原因model3.json 里 Textures 数组写的路径是相对 json 所在目录计算的。从资源站或解包目录复制模型时经常出现模型 json 在根目录、贴图在子目录但 json 里写的是另一个相对路径目录一变所有引用全部失联。另一种情况是贴图超大2048 以上的纹理在部分移动端 GPU 上分配失败表现也是花屏或完全不渲染。解决先查 Network 面板找到 404 的那条请求对照 json 里的 Textures 数组修正路径。如果路径都对了还是花把 2048 的贴图压到 1024 再试。用 Python PIL 几行代码就能批量处理from PIL import Image import glob for f in glob.glob(./model/texture_*.png): img Image.open(f) img img.resize((1024, 1024)) img.save(f)压完重启服务再看花屏大概率消失。这是 Live2D 集成里出现频率最高的两类贴图问题排查顺序永远是先看路径再看尺寸。4.3 看板娘挡住页面按钮和滚动事件热区与 z-index 控制现象页面右下角的看板娘看着不大但它所在的那块矩形区域里按钮点击失灵、滚轮滚不动像有一块透明玻璃罩在上面。原因如果 PIXI.Application 用了 resizeTo: window或者 CSS 把 canvas 撑满全屏canvas 的热区会覆盖整页。Live2D 模型本身只在右下角但 canvas 对鼠标事件的捕获范围是全屏的。这是新手最容易忽略的交互层级问题。解决把 canvas 尺寸收敛到看板娘实际占用的区域用固定尺寸定位。#live2d-canvas { position: fixed; right: 20px; bottom: 0; width: 240px; height: 320px; pointer-events: auto; /* 画布区域保留交互 */ z-index: 1000; }如果希望看板娘纯展示不参与交互直接设pointer-events: none画布会穿透所有鼠标事件页面按钮和滚动恢复正常。代价是看板娘也没法点击和拖拽。想同时保留交互又不挡页面只能把 canvas 做得刚好贴合模型外框别留大块透明区域。这个取舍没有标准答案取决于你的页面布局。4.4 老 .moc 模型加载后一片空白入口脚本版本不匹配现象模型是从旧项目完整复制过来的文件路径也没错但加载后页面啥都没有控制台也没有明显的红色报错。原因pixi-live2d-display 对 Cubism 2 和 Cubism 4 提供完全不同的构建入口。cubism2.min.js 对应老格式cubism4.min.js 对应新格式。拿 .moc 老模型配 cubism4.min.js加载器在 json 解析阶段就判定链路不匹配表现就是空白连报错都不给。解决看一眼模型入口文件是 .model.json 还是 .model3.json前者用 cubism2.min.js后者用 cubism4.min.js。这个坑我踩过一次后把判断方法固化成了习惯下载任何模型第一眼先看入口 json 是几代格式再决定引哪个脚本。这个习惯能帮你省掉后续一长串的黑色排查时间。4.5 加载瞬间闪白块、高分屏边缘发虚透明背景与分辨率现象刷新页面时看板娘出现之前右下角先闪一个白色矩形。另外在高分屏上模型边缘有锯齿像没开抗锯齿。原因白块是 canvas 默认底色不是透明加上模型异步加载期间 canvas 先显示出来了。锯齿则是分辨率适配问题canvas 的 CSS 尺寸和物理像素没有一一对应导致渲染采样率不足。解决Application 配置里同时写backgroundAlpha: 0和transparent: true覆盖不同版本 PixiJS 的兼容差异。还压不住的话把 canvas 容器初始设为不可见等模型 loaded 事件后再显示。style #live2d-canvas { visibility: hidden; } /style script model.on(loaded, () { document.getElementById(live2d-canvas).style.visibility visible; }); /script锯齿问题则把 resolution 设成与 devicePixelRatio 匹配的值比如 2配 autoDensity: true 一起用。代价是内存和 GPU 开销上涨低端机建议保持 1清晰度和性能之间得有个取舍。5. 从 demo 到可用组件拖拽、事件联动与性能底线5.1 给看板娘加拖拽三行事件核心逻辑跑通了 demo下一步通常是想让它能拖。pixi-live2d-display 默认不处理拖拽但用 PixiJS 自带的交互事件补齐很容易核心逻辑是按下时记录偏移移动时改位置抬起时清理状态。model.eventMode static; // 允许模型接收事件PixiJS 6 里可用 interactive true model.cursor grab; model.on(pointerdown, (e) { model.offsetX e.data.global.x - model.x; model.offsetY e.data.global.y - model.y; model.cursor grabbing; }); model.on(pointermove, (e) { if (model.offsetX ! undefined) { model.x e.data.global.x - model.offsetX; model.y e.data.global.y - model.offsetY; } }); model.on(pointerup, () { model.offsetX undefined; model.offsetY undefined; });这段代码放在模型加载完成后执行即可。注意拖拽会把模型移出画布范围如果 canvas 是固定尺寸的拖出去就回不来了。我一般会限制 x、y 的最小最大值让模型拖不出右下角区域用户也不至于把角色弄丢。5.2 用业务按钮驱动角色动作并加语音反馈第二个落地技巧是让看板娘响应页面业务。最常见的场景是点击「帮助」按钮时角色做一个说话动作再用浏览器自带语音把文案读出来。SpeechSynthesis 是浏览器原生能力不需要接任何第三方 SDK。// 按钮点击让角色说话并播放动作 helpBtn.addEventListener(click, () { model.motion(TapBody); const utterance new SpeechSynthesisUtterance(欢迎来到我的页面有什么可以帮你); utterance.lang zh-CN; utterance.rate 1.0; window.speechSynthesis.speak(utterance); });动作组名同样以模型 json 里 Motions 字段的实际命名为准没有合适的说话动作就退化成播 Idle 组里的某个摆手动作效果上差别不大。这套联动做出来之后看板娘就从装饰变成了能承载业务状态的提示器。最后一件事是性能底线。Live2D 再轻也是 WebGL 实时渲染GPU 每帧都要跑变形计算。我给自己定的规矩是页面主内容渲染超过 200ms 或帧率长期低于 40fps 的项目不加看板娘加了之后一定做前后对比而不是凭感觉判断。低端安卓机上 2048 贴图加 60fps 的渲染压力一叠加掉帧和发热是肉眼可见的。大多数情况下把贴图压到 1024、限制帧率到 30、关掉物理模拟观感损失不大但设备负担能降一大截。我现在的习惯是每次往项目里加这种「锦上添花」的组件前先在加载性能和交互侵占性上各过一次评估。Live2D 能提沉浸感前提是页面本身的体验没被它拖垮这个顺序不能反。希望帮到你。本文还有配套的精品资源点击获取