简介:面向网页开发者的Live2D集成示例工程,演示如何将二维动画角色嵌入HTML页面并实现触摸交互、动态更换人物等效果。压缩包共612个文件、17.29MB,以mtn动作数据、json模型配置、png贴图、mp3音频和moc模型文件为主,另含2个html入口、2个js脚本及1份md说明;从引入Live2D库、创建Canvas画布,到解析模型配置、初始化渲染引擎,再到监听点击滑动事件,均提供可直接运行的代码路径。资源还覆盖了运行时替换模型、与其他页面组件联动触发角色动作等进阶用法,并附带多个角色模型和语音素材,方便对比不同配置效果、理解模型资源组织方式。整体结构清晰,注释与说明文字能够辅助开发者快速理解代码、排查集成中的常见问题,适合希望在游戏、教育或娱乐网站中加入互动角色的初中级前端开发者。已有1431人学习下载,是一份兼顾演示与二次开发参考的实用资料。
1. 为什么要在 HTML 里集成 Live2D:这个 demo 能拿来做什么
如果你搜过「html 如何放一个会动的看板娘」,八成见过别人博客右下角那个会眨眼、会跟着鼠标扭头的小人。那个东西就是 Live2D 模型,而它所在的页面本质上就是一段普通的 HTML。html集成live2D,demo这个标题,指的是把 Live2D 模型通过 Web 端 SDK 嵌进网页,跑一个最小可用的示例,让你能从零看到模型渲染、交互、动作触发这一整套流程跑通。
这个方案最常见的落地场景是个人网站和官网的访客陪伴角色,或者产品引导页里的虚拟助手。相比直接嵌入一段 GIF 或者视频,Live2D 的优势在于模型体积小、WebGL 渲染流畅、支持实时交互,而且背后有成熟的 Cubism 编辑器生态,美术资源可以直接复用。适合的人群也很明确:前端开发想把「会动的人物」加到页面上,但又不想从零折腾 WebGL;以及独立开发者想做一个带虚拟形象的官网 demo,先验证交互和视觉方案是否可行。
这篇笔记会从 Web 端 Live2D 的渲染原理讲起,然后给你一套能直接复制到本地跑通的最小 demo,再到交互、参数调优和常见坑。全程按我实际做过的方式来讲,你跟着走完,一个能点击、能拖拽、能换表情的 Live2D 角色就能出现在你自己的 HTML 页面里。
2. 弄清 Live2D 在浏览器里怎么跑:核心依赖与选型依据
2.1 Web 端 Live2D 的两条技术路线
Live2D 在浏览器里跑,本质上是一套 WebGL 渲染流程:模型文件描述网格和纹理,SDK 读取这些数据,在 Canvas 上做顶点变形和纹理绘制。这里有两个核心选择:Cubism 原生 SDK和基于 PixiJS 的第三方封装库。
Cubism 原生 SDK 是官方提供的 Web 运行时,核心文件是live2d.min.js,你需要自己搭场景、管理 Canvas、绑定交互事件。它功能最全,但上手成本高,文档偏日式说明风格,示例散落在官方 demo 里,新手抄起来比较费劲。
第三方封装库目前最常见的是pixi-live2d-display。它把 PixiJS 的展示对象体系跟 Live2D 模型封装在一起,开发者只需要new Live2DModel()然后addChild,交互事件直接复用 PixiJS 的事件模型,加载管理、自动缩放、动作触发都有现成 API。
我做 demo 时选择的路线是后者。原因很简单:demo 的目标是快速看到效果、跑通交互,而不是研究 SDK 底层。如果你想做深度定制(比如自己实现口型同步、多层场景合成),再考虑换原生 SDK 不迟。两者的取舍可以这样理解:
| 对比项 | Cubism 原生 SDK | pixi-live2d-display |
|---|---|---|
| 渲染内核 | 官方 WebGL 运行时 | PixiJS + 官方 Core |
| 上手成本 | 高,需自行管理场景 | 低,组件化封装 |
| 交互支持 | 需手动绑定 | 内置拖拽、点击、聚焦 |
| 模型版本 | Cubism 2 / 3 / 4 均可 | 依赖官方 Core 支持 |
| 定制自由度 | 最高 | 受封装限制,但够用 |
2.2 模型文件格式:你手里的资源包到底有什么
拿到一个 Live2D 模型,你看到的是一堆文件,而不是单个文件。一个标准的 Cubism 4 模型包含:
xxx.model3.json:模型入口文件,声明了贴图、物理、表情、动作等所有资源的索引xxx.moc3:模型本体数据,包含网格和变形参数- 多张
.png贴图:角色的各部位纹理 .physics3.json:物理模拟参数(头发、胸、飘带的摆动).exp3.json:表情预设.motion3.json:动作预设(闲置、点击、说话等)
pixi-live2d-display加载时只需要给Live2DModel.from()传入.model3.json的路径,它会自动读取内部索引,加载同目录下的其他资源。如果你拿到的模型是 Cubism 2 格式(.model.json+.moc),这个库也能兼容,因为底层 Core 会做格式区分。
注意,模型资源本身受版权和许可协议约束。demo 阶段建议优先使用 Live2D 官方示例模型,或你所在团队自制的模型。如果使用网上流传的模型包,务必先确认其个人使用授权范围,否则放到公开站点会留下隐患。
2.3 关键依赖:SDK Core 与运行时库的关系
pixi-live2d-display本身只是封装层,真正的渲染引擎是 Live2D 官方 Core。也就是说,页面里至少需要两个库:
- 官方的
live2dcubismcore.min.js(负责解析 moc3 数据和 WebGL 渲染) @pixi/app、pixi.js以及pixi-live2d-display(负责场景管理和交互)
在 demo 阶段,你可以直接用 CDN 引入,不用搭建前端构建链路。但生产环境建议通过 npm 安装,并且用构建工具打包,方便做版本锁定和资源优化。
3. 从零搭一个最小 demo:三分钟跑通一个会动的角色
3.1 准备一个干净的 HTML 文件
先建一个工作目录,把模型资源丢进去,然后新建index.html。整个 demo 的骨架就是把三个库依次引入,创建一个 PixiJS 应用,再加载模型。我习惯把初始化逻辑单独拆到一个main.js里,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>HTML 集成 Live2D Demo</title> <style> body { margin: 0; overflow: hidden; background: #2d2d2d; } #live2d-canvas { position: fixed; bottom: 0; right: 0; width: 300px; height: 300px; cursor: grab; } </style> </head> <body> <canvas id="live2d-canvas"></canvas> <!-- PixiJS 核心 --> <script src="https://cdn.jsdelivr.net/npm/pixi.js@7.2.4/dist/browser/pixi.min.js"></script> <!-- Live2D 官方 Core,必须是这个全局变量名 --> <script src="https://cdn.jsdelivr.net/npm/live2dcubismcore@4.2.1/live2dcubismcore.min.js"></script> <!-- pixi-live2d-display 封装层 --> <script src="https://cdn.jsdelivr.net/npm/pixi-live2d-display@0.4.0/dist/index.min.js"></script> <!-- 你自己的初始化脚本 --> <script src="./main.js"></script> </body> </html>这里有一个关键顺序:live2dcubismcore.min.js必须在pixi-live2d-display之前加载。因为封装库初始化时会检测全局对象上是否存在Live2D这个命名空间,检测不到就会直接抛错。这只是个 demo,所以我用 CDN 省去打包步骤,但你要知道,浏览器上通过 CDN 引入的PIXI和Live2D都会挂到window上,后续main.js直接用全局变量访问。
3.2 用最少的代码启动 PIXI 应用并加载模型
main.js里的工作可以分为三步:创建 PIXI 应用、实例化 Live2D 模型、把模型加进舞台。下面这段代码是我调试后的最小可用版本:
// main.js // 1. 创建 PIXI 应用,绑定到页面上已有的 canvas const app = new PIXI.Application({ view: document.getElementById('live2d-canvas'), width: 300, height: 300, transparent: true, // 让 canvas 背景透明 autoStart: true, antialias: true }); // 2. 加载模型 async function loadModel() { try { const model = await PIXI.live2d.Live2DModel.from( './model/haru_greeter/haru_greeter.model3.json' ); // 3. 把模型锚点放在底部,方便跟页面贴合 model.anchor.set(0.5, 0.9); model.scale.set(0.6); model.position.set(app.screen.width / 2, app.screen.height - 5); app.stage.addChild(model); return model; } catch (error) { console.error('模型加载失败:', error); } } loadModel();Live2DModel.from()返回的是一个 Promise,这也就意味着模型加载是异步的,必须放在async函数里等待。
参数说明:
anchor.set(0.5, 0.9)控制模型中心点。Live2D 模型的底部通常是脚底,锚点放在 0.9 附近位置,模型就不会悬空,而是像站在 canvas 底部一样。scale控制缩放比例。这个数值取决于你的模型原始尺寸跟 canvas 尺寸的比例,Haru 这种标准人物模型,在 300 宽的 canvas 里 0.6 是合适起步值。position设置模型在舞台中的坐标。app.screen就是 canvas 的宽高,水平居中,垂直方向留 5 像素让它稍微高于边界。transparent: true会让背景完全透明,网页底色能看到,这是看板娘方案里几乎必须的选项。如果不设置,canvas 默认是黑色背景,会挡住页面内容。
3.3 调整加载路径与静态资源服务方式
Live2DModel.from()的路径是相对当前页面 URL 的。如果你直接双击打开index.html,文件协议访问时的行为会比较诡异,有些浏览器会限制本地资源加载,模型大概率加载不出来。最简单的做法是起一个本地静态服务,在项目目录下执行:
# 用 python 起一个静态文件服务,端口 8000 python3 -m http.server 8000然后浏览器访问http://localhost:8000/index.html,模型路径/model/haru_greeter/xxx.model3.json就会以根目录来解析。
我之前犯过的错是直接用file://协议打开页面,结果 console 里报Failed to load resource,以为是代码写错了,实际是资源服务方式不对。以后凡是涉及本地加载外部的模型等资源文件,都默认先起一个 HTTP 服务,少踩很多无所谓的坑。
4. 给 demo 加上交互:点击反馈、拖拽视角和自动说话
4.1 绑定点击事件与表情切换
模型加载成功只是第一步,demo 的价值在于让访客感觉到「这个角色是活的」。最简单的交互就是点击角色时切换一个表情。pixi-live2d-display为模型提供了expression()方法,可以直接用过模型配置文件里的表情 ID 触发切换:
// 点击切换表情 model.on('pointerdown', () => { // 读取 model3.json 里的表情列表,取第一个表情 ID const expressions = model.internalModel.motionManager.expressionManager.definitions || []; if (expressions.length > 0) { model.expression(expressions[0].name); } });这里的definitions是从model3.json里的Expressions字段解析出来的对象数组,每个对象有name和file两个属性。如果你知道自己要用的表情名,比如模型默认带Happy表情,可以直接传model.expression('Happy'),不需要每次都去遍历。
有一个重要的参数细节:expression()触发要不要加淡入淡出过渡,取决于模型本身的参数设置以及切换目标是否跨组。默认情况下,切换表情会有一小段过渡动画,你不需要额外处理。如果切换后发现表情变了但动作太突兀,检查模型的model3.json里Expressions是否设置了FadeInTime,这个值可以整体控制过渡时长,改成 0 就是瞬时切换。
4.2 拖拽移动和视角跟随
拖动是 Live2D 看板娘交互里最典型的动作。用户按住角色拖动,模型能跟着走,同时模型的眼睛还会追踪鼠标位置。这块封装层已经把底层事件做好了,你只需在model上绑定:
// 让角色可以拖拽移动 model.on('pointerdown', event => { model.dragging = true; model.offsetX = event.data.global.x - model.x; model.offsetY = event.data.global.y - model.y; }); model.on('pointermove', event => { if (model.dragging) { model.x = event.data.global.x - model.offsetX; model.y = event.data.global.y - model.offsetY; } }); model.on('pointerup', () => { model.dragging = false; });注意这段代码里的offsetX/offsetY是我额外定义的,不是库自带的属性。因为它计算了鼠标按下的偏移量,这样拖拽时模型的中心不会突然跳到鼠标位置,而是保持一个相对稳定的抓取关系。
如果你想保持模型在页面上的可见区域范围内,可以加一个约束判断:
model.on('pointermove', event => { if (!model.dragging) return; const newX = event.data.global.x - model.offsetX; const newY = event.data.global.y - model.offsetY; model.x = Math.min(Math.max(newX, 0), app.screen.width); model.y = Math.min(Math.max(newY, 0), app.screen.height); });4.3 让角色自动说话和随机行动
一个一直眨眼、偶尔挥挥手的角色,会比一动不动的更有存在感。pixi-live2d-display提供了motion()方法,可以播放模型自带的.motion3.json动作:
// 在背景播放一段闲置动作,循环模式 model.motion('Idle', 0);第一个参数对应model3.json里的Motions分组名,第二个参数是该组内第几个动作。如果你想让模型随机做动作,可以监听motionFinish事件:
model.on('motionFinish', () => { // 随机挑选一个 "Tap" 组动作来播放 const randomIdx = Math.floor(Math.random() * 3); model.motion('Tap', randomIdx); });这里需要看一眼model3.json里Motions的定义结构,不同模型的组名差别很大,有的叫Idle,有的叫TapBody。先console.log(model.internalModel.motionManager.definitions)看看实际加载了哪些动作组,再写触发逻辑会快很多。
5. 集成避坑指南:这几个问题最容易让新手翻车
5.1 加载模型时报 404,但路径看起来没问题
现象:模型不显示,Console 里出现404 Not Found,然后Live2DModel.from()一直处于 pending 状态。
原因:最常见的两个原因。第一,你用的是相对路径,但页面不是通过 HTTP 服务打开的,file://协议下浏览器限制本地跨文件访问。第二,即使你在 HTTP 服务下,model3.json里引用的其他资源(贴图、moc3)也可能使用相对路径,这个相对路径是相对于.model3.json文件所在目录的,如果你把模型文件分散放置,就会造成某个子资源 404。
解决:统一把模型目录保持原来的结构,一个模型的文件夹整体放到项目的model/目录下,不要单独抽出贴图或者把.moc3换个位置。服务上确认路径大小写没有错,Windows 下开发时大小写不敏感,Linux 服务器上就可能直接 404。
5.2 Canvas 被旁边元素挤掉了,透明区域挡住页面内容
现象:模型出来了,但 canvas 有一大块不可点击的透明区域,挡住页面上其他按钮,导致页面无法交互。
原因:canvas 默认是矩形盒子,即使内容是透明的,盒子区域依然存在,并且会覆盖在同级的其他元素之上。而 canvas 的 width 和 height 如果设置太大,或者定位方式不对,就会挡住下方的导航或其他内容。
解决:调整定位方式和尺寸。把 canvas 定位为fixed并且设成pointer-events: none,这样它就不接收鼠标事件了,但模型本身需要交互。正确的做法是把 canvas 的尺寸设置成刚好包住模型,style里用width: 300px; height: 300px,并且把z-index放在合适层级,不要盖住主要内容。如果你需要模型占的空间更小,可以用 CSS 缩放整个 canvas。
5.3 模型在低配设备上帧率暴跌、风扇狂转
现象:手机或者集显笔记本上页面卡顿明显,模型动作一多就掉帧。
原因:Live2D 是 WebGL 实时渲染,每个模型都在消耗 GPU 资源。如果你开了antialias,再加多个模型互层叠加,低端设备吃不消。
解决:这是纯理论问题,没有标准内置开关,一般做法是按设备能力开不同档位。在初始化时判断设备 GPU:
const isLowEnd = !window.WEBGL_DEBUG || (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4); const antialias = !isLowEnd; const app = new PIXI.Application({ view: document.getElementById('live2d-canvas'), antialias: antialias, autoStart: true });另外,如果 canvas 实际显示尺寸是 300px,但渲染分辨率很高,会加速 GPU 消耗。可以在创建应用的时候把resolution设为window.devicePixelRatio的一半,视觉上变化不大,性能会明显好转。
5.4 移动端模型糊成一团、眼睛位置不对
现象:模型在桌面浏览器正常,但在移动端上看,贴图有模糊感或者眼睛拉扯得厉害。
原因:Live2D 的模型贴图是为特定分辨率设计的,你把它缩放得太小,就可能出现纹理采样模糊。更常见的问题是模型的layout参数没配好,比如你只改了scale,但忽略了 canvas 尺寸和model.position之间的配合,导致视觉重心偏移。
解决:优先参考model3.json里的Layout字段,里面有模型原设计时的宽高比。保持 canvas 宽高跟这个比例接近,再调scale和anchor。移动端建议用PIXI.live2d.Live2DModel.from的第二个参数自动适配,比如传{ autoInteract: true }让模型自动响应触摸事件,并固定 canvas 尺寸与 CSS 像素一致,避免高分屏下模糊。
5.5 Live2D 模型无法加载,报Live2D is not defined
现象:页面报 ReferenceError,Live2D这个变量不存在。
原因:官方 Core 的文件没有成功加载,或者加载顺序错了。live2dcubismcore.min.js必须在pixi-live2d-display之前执行,否则封装层找不到底层依赖。
解决:检查 script 标签顺序,确认 CDN 链接能正常访问(国内网络环境可能需要换一个能访问的 CDN 源)。如果你是把库打入 npm 包,确认live2dcubismcore的版本和pixi-live2d-display要求的 Core 版本匹配,不匹配也会导致同样的报错。
6. 验证成色:把你的 demo 从「能跑」推进到「值得展示」
6.1 用模型聚焦和透明度做场景收尾
当你已经跑通了加载、拖动、表情切换,接下来最有用的一点是让模型在画面里的存在感更自然。一个加分小技巧是让模型自动追踪鼠标位置,形成一种「它在看你」的错觉。pixi-live2d-display内置了一个focus插件,启用后模型的视线会跟随鼠标:
model.focus = { x: 0.5, y: 0.5 };这个值表示视线的初始朝向,配合model.autoUpdateFocus,模型就会自动追踪鼠标的位置变化。实际使用中我还会加一个透明度渐变,当页面滚动到其他区域时让角色淡出,避免一直在角落里干扰阅读:
window.addEventListener('scroll', () => { const opacity = 1 - Math.min(window.scrollY / 400, 0.7); model.alpha = opacity; });6.2 多模型轮换:一个 canvas 内切换角色
如果你的项目需要多个角色切换,不需要销毁重建整个 PIXI 应用。因为app.stage是一个容器,你可以在同一个 canvas 内先removeChild旧模型,再addChild新模型。切换时,旧模型的destroy()方法会释放 GPU 纹理,避免内存持续增长。
async function switchModel(newUrl) { if (currentModel) { app.stage.removeChild(currentModel); currentModel.destroy(); } currentModel = await PIXI.live2d.Live2DModel.from(newUrl); currentModel.anchor.set(0.5, 0.9); currentModel.scale.set(0.6); currentModel.position.set(app.screen.width / 2, app.screen.height - 5); app.stage.addChild(currentModel); }这里检查destroy()是否被正确调用,可以用浏览器 DevTools 的 Performance 面板看 GPU 内存曲线。如果切换几次后曲线持续走高,说明之前的模型纹理没有释放干净,通常是因为还有事件监听没有移除。你可以在销毁前手动off()掉所有自定义监听,再调用destroy()。
6.3 一个提醒:Demo 值不值得变成线上方案
到了这一步,你已经拥有了一个完整的 HTML 集成 Live2D 的 demo。投入生产之前,还有三件事要确认:模型版权是否允许网页形式使用、模型文件体积是否适合线上加载(一般会做纹理压缩和合并动作文件)、以及你的站点是否真的需要一个看板娘。Live2D 适合内容引导、互动氛围强的页面,如果你的页面是纯信息展示型,它带来的视觉增益有限,反而增加加载负担。我的习惯是:demo 阶段能用 CDN 直接跑的,生产环境一定换成 npm 依赖、本地托管模型资源、设置好合理的缓存策略。这样既能保证加载速度,也能避免受第三方 CDN 可用性波动的影响。
希望这篇笔记能帮你把 html 里的 Live2D 跑起来,让你少走几趟加载报错和位置不对的弯路。
本文还有配套的精品资源,点击获取