本文所使用的前端代码已开源,并新增 AI 聊天功能,开源地址为:LuoTian001/live2d-widget-AIChat
本文教程仅包含基础看板娘功能及其配置方法。若想了解如何在此基础上集成 AI 聊天功能,请参考上述开源项目的 README 文档和部署教程:Live2D AI 聊天功能配置教程 — 基于 FastAPI + DeepSeek

一、部署原理

  在hexo + butterfly博客中部署Live2D前端组件,本质是通过引入基于WebGL的JavaScript库,在网页的<canvas>标签中解析并绘制模型文件。实现过程主要分为模型获取、引擎选择与网页部署三个环节。
  Live2D模型分为早期版本Cubism 2.0和现代版本Cubism 3.0+,两者在数据结构和渲染管线设计上存在显著差异。Cubism 2.0的模型文件 .moc 结构较为简单,渲染管线也更为直接。Cubism 3.0+ 引入了更复杂的物理模拟、变形算法和动画系统,其模型文件 .moc3 在解析时需要更高的计算资源和更复杂的渲染逻辑。本教程使用moc3模型及其相关资源。
  本教程参考项目 Live2d-Widget-v3,该项目是基于 live2d-widget项目的二次开发,作者在保留live2d-widget核心功能的基础上,接入了对 Cubism 3.0+ 标准的 .moc3 模型文件的支持。可以更加方便地在博客中部署看板娘。

二、模型获取

  常用的Live2D模型资源平台包括 Live2D Cubism 官方资源库BoothNizima。或者在 GitHub 搜索 live2d models moc3,国内用户也可以在 Bilibili 搜索Live2D模型配布moc3 免费模型选择自己喜欢的模型。在这些平台上可以找到大量免费和付费的Live2D模型资源。
  资源下载时请确保选择包含.moc3文件的资源包,并注意相关版权和使用许可。这里我以Bilibili的一个免费模型 Allium为例(作者:Yuri幽里_official),下载后解压得到以下文件结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ariu/
├── ariu.moc3
├── ariu.model3.json
├── ariu.physics3.json
├── motions/
│ ├── jk包.exp3.json
│ ├── 戴帽子.exp3.json
│ ├── 黑化.exp3.json
│ └── ...
├── textures/
│ ├── texture_00.png
│ ├── texture_01.png
│ ├── texture_02.png
│ └── texture_03.png

  • ariu.moc3是模型的核心文件,包含模型的骨骼、网格、物理属性等信息
  • ariu.model3.json定义模型的结构和资源引用
  • ariu.physics3.json包含物理模拟参数
  • motions/目录下存放不同表情的动画文件*exp3.json
  • textures/目录下存放模型使用的纹理图片

  一些模型还包含下面的文件,这些文件不是必需的,但可以增强模型的表现力和交互性。

  • *.pose3.json定义模型的默认姿势
  • *.idle_motion3.json定义模型的默认动画
  • *.motions.json定义模型的动作集合
  • *.hit_area3.json定义模型的交互区域
  • *.user_data3.json包含用户自定义数据

三、模型优化

3.1 贴图资源优化

  考虑到Web端对网络请求的敏感性,模型中庞大的 .png 贴图会严重拖慢页面的首屏绘制时间,因此使用格式工厂等软件将模型 textures 目录下的所有 .png 文件转换为高压缩比的 .webp 格式,并修改ariu.model3.json中对应的纹理路径引用:

1
2
3
4
5
6
7
8
{
"textures": [
"textures/texture_00.webp",
"textures/texture_01.webp",
"textures/texture_02.webp",
"textures/texture_03.webp"
]
}

  这一步不是必须,你也可以选择将整个模型资源(包括贴图、动画等资源)部署在CDN或推送到github公共仓库,并在后续配置中修改路径为CDN地址以加速加载。

3.2 添加动画资源

  默认情况下live2d-widget只会加载默认表情动画,因此需要在模型配置ariu.model3.json中添加motions文件夹中表情动画文件.exp3.json的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"FileReferences": {
"Moc": "ariu.moc3",
"Textures": [
// 这里是textures目录下的纹理文件列表
],
"Physics": "ariu.physics3.json",
"DisplayInfo": "ariu.cdi3.json",
"Expressions": [ // 这里仅展示部分表情动画文件的引用
{
"Name": "jk",
"File": "motions/jk包.exp3.json"
},
{
"Name": "love",
"File": "motions/爱心眼.exp3.json"
},
{
"Name": "hat",
"File": "motions/戴帽子.exp3.json"
}
]
},

  其中Expressions字段定义模型的表情动画集合,每个表情动画包含一个名称Name和对应的动画文件路径File。这样在后续配置中就可以通过表情名称来触发表情切换。如果你有其他动画资源,可参考Live2d-Widget-v3 readme的2.3.3节进行配置。

3.3 添加模型画布配置

  Live2d-Widget-v3 支持通过额外配置来调整画布中模型的大小和位置。在ariu/下添加config.json文件,内容如下:

1
2
3
4
5
6
7
{
"scale": 1.0,
"translate": {
"x": 0.0,
"y": 0.0
}
}

  后续如果觉得模型偏大、偏小或者偏离中心,可以通过调整这几个浮点数来修正矩阵参数。其中scale控制模型的整体缩放比例,translate.xtranslate.y分别控制模型在水平和垂直方向上的平移偏移量。调整这些参数可以让模型在画布中以最佳状态展示。

四、文件结构配置

  Hexo 会将 source 目录下的文件视为静态资源映射到网站根目录。我目前使用的是“引擎代码走 CDN,模型资源走本地”的方式。在博客根目录 source/ 下创建 live2d/ 目录。在目录中创建model_list.json文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"models": [
[
"ariu"
]
],
"messages": [
[
"来自 BiliBili 的 ariu 酱 ~"
]
]
}

  其中models字段定义模型列表,每个模型由一个包含模型文件夹名称的数组表示;messages字段定义模型的欢迎语列表,每个欢迎语由一个包含文本字符串的数组表示。如果你有多个模型,可以在models数组中添加更多的模型文件夹名称,并在messages数组中添加对应的欢迎语,可以参考项目Live2d-Widget-v3 readme的2.3.2节进行配置。
  然后在 Live2d-Widget-v3中下载waifu-tips.jswaifu-tips.jsonwaifu.css三个文件,放在 source/live2d/ 目录下。将模型资源放在 source/live2d/model 目录下。最终的文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
source/
├── live2d/
│ ├── waifu-tips.js
│ ├── waifu-tips.json
│ ├── waifu.css
│ └── model/
│ └── ariu/
│ ├── ariu.moc3
│ ├── ariu.model3.json
│ ├── ariu.physics3.json
│ ├── config.json
│ ├── motions/...
│ └── textures/...

五、网页部署

  首先在博客的_config.yml中跳过对source/live2d/目录的渲染,添加以下配置:

1
2
skip_render:
- live2d/**

  这样Hexo在构建时就不会对live2d/目录下的文件进行处理,确保模型资源能够正确地被部署到网站根目录下的/live2d/路径。
  然后在butterfly主题的_config.butterfly.yml文件定位到 inject.bottom 节点,将核心调度代码按照 YAML 的多行字符串格式进行DOM注入,相关参数说明见注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
inject:
bottom:
- |
<script>
// PJAX 单例锁,保证 Live2D 核心逻辑在生命周期内只初始化一次,避免重复加载和资源泄漏
if (typeof window.live2d_initialized === 'undefined') {
window.live2d_initialized = true;
// Live2D Widget v3 配置对象
const cdnPath = "https://cdn.jsdelivr.net/gh/letere-gzj/live2d-widget-v3@main";
const config = {
path: {
homePath: "/", // 网站根路径
modelPath: "/live2d/", // live2d 资源与配置路径
cssPath: "/live2d/waifu.css", // live2d 样式表路径
tipsJsonPath: "/live2d/waifu-tips.json", // live2d 提示语配置路径
tipsJsPath: "/live2d/waifu-tips.js", // live2d 提示语脚本路径
live2dCorePath: cdnPath + "/Core/live2dcubismcore.js", // Live2D核心库路径,使用 CDN 加载
live2dSdkPath: cdnPath + "/live2d-sdk.js" // Live2D SDK路径,使用 CDN 加载
},
tools: ["hitokoto", "express", "info", "quit"], // 前端工具栏按钮配置,我在这里做了精简
drag: { enable: false, direction: ["x", "y"] }, // 拖拽配置,这里为模型禁止拖拽
switchType: "order" // 模型切换方式,这里为顺序切换
};
// 资源初始化以及移动端适配
if (screen.width >= 768) {
Promise.all([
loadExternalResource(config.path.cssPath, "css"),
loadExternalResource(config.path.live2dCorePath, "js"),
loadExternalResource(config.path.live2dSdkPath, "js"),
loadExternalResource(config.path.tipsJsPath, "js")
]).then(() => {
if (typeof initWidget !== "undefined") {
initWidget({
waifuPath: config.path.tipsJsonPath,
cdnPath: config.path.modelPath,
tools: config.tools,
dragEnable: config.drag.enable,
dragDirection: config.drag.direction,
switchType: config.switchType
});
}
});
}
// 异步加载资源
function loadExternalResource(url, type) {
return new Promise((resolve, reject) => {
let tag;
if (type === "css") {
tag = document.createElement("link");
tag.rel = "stylesheet";
tag.href = url;
} else if (type === "js") {
tag = document.createElement("script");
tag.src = url;
}
if (tag) {
tag.onload = () => resolve(url);
tag.onerror = () => reject(url);
document.head.appendChild(tag);
}
});
}
}
</script>

  上述inject相关设置我做了自定义和简化,更详细的参数配置详见Live2d-Widget-v3 readme的2.1和2.2节。然后hexo clean && hexo g重新构建博客,到这里就能在页面中看到部署成功的Live2D看板娘了。

六、进阶配置

6.1 模型视图配置

  模型的位置默认是在左下角,如果想将其移动到右下角,需修改 source/live2d/waifu.css,同时针对 Butterfly 主题右侧的悬浮按钮(如回到顶部、夜间模式)留出安全距离。在waifu.css中找到 #waifu#waifu-tool 这两个选择器,进行如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#waifu {
bottom: -1000px;
left: auto;
right: 50px;
line-height: 0;
margin-bottom: -10px;
position: fixed;
transform: translateY(3px);
transition: transform .3s ease-in-out, bottom 3s ease-in-out;
z-index: 10;
}
#waifu-tool {
color: #aaa;
opacity: 0;
position: absolute;
right: auto;
left: 0px;
top: 55px;
transition: opacity 1s;
}

  这样修改后模型就会出现在右下角,并且工具栏也会相应地调整到模型左侧,避免与Butterfly主题的悬浮按钮发生重叠。

6.2 模型画布配置

  由于这个模型是全身模型,默认配置下人物的表情细节不够清晰。修改模型的config.json使得模型呈现半身照效果。

1
2
3
4
5
6
7
{
"scale": 2.0,
"translate": {
"x": 0.0,
"y": -0.8
}
}

6.3 模型提示语配置

  waifu-tips.json配置一系列鼠标悬停事件的触发条件和对应的提示语响应。原项目中的配置是针对 NexT 主题等设计的,与 Hexo + Butterfly 主题的 HTML 结构和 class 命名规范完全不同。因此需要对这些触发条件进行调整,确保它们能够正确地映射到 Butterfly 主题的核心 DOM 元素上,从而在与页面交互时能够触发相应的提示语。将/source/live2d/waifu-tips.json文件中mouseover数组进行替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
"mouseover": [
{
"selector": "#live2d",
"text": ["干嘛呢你,快把手拿开~~", "鼠…鼠标放错地方了!", "你要干嘛呀?", "喵喵喵?", "怕怕(ノ≧∇≦)ノ", "非礼呀!救命!", "这样的话,只能使用武力了!", "我要生气了哦", "不要动手动脚的!", "真…真的是不知羞耻!", "Hentai!"]
},
{
"selector": "#site-name",
"text": ["点击这里可以回到主页哦!", "这可是主人的门面呢!"]
},
{
"selector": "#search-button",
"text": ["找不到想看的内容?搜索看看吧!", "在找什么东西呢,需要帮忙吗?"]
},
{
"selector": ".menus_item a[href='/']",
"text": ["点它可以回到首页啦!", "想回到上一页可以使用浏览器的后退功能哦。"]
},
{
"selector": ".menus_item a[href='/archives/']",
"text": ["文章目录都整理在这里啦!", "来看看主人的历史记录吧!"]
},
{
"selector": ".menus_item a[href='/tags/']",
"text": ["点击就可以看文章的标签啦!", "来看看都有哪些标签吧~"]
},
{
"selector": ".menus_item a[href='/categories/']",
"text": ["文章都分类好啦~", "来看看都有哪些分类吧~"]
},
{
"selector": ".avatar-img",
"text": ["我家主人好看吗?", "这是我家主人(*´∇`*)", "发现主人出没地点!"]
},
{
"selector": ".card-info-data a",
"text": ["这是文章的统计信息~", "这里记录着主人的心血哦!"]
},
{
"selector": ".card-info-social-icons a",
"text": ["这里有主人的联系方式!", "要去拜访一下主人的其他阵地吗?"]
},
{
"selector": ".article-title",
"text": ["要看看 <span>{text}</span> 这篇文章吗?", "这篇好像很有意思呢!"]
},
{
"selector": ".article-meta-wrap a",
"text": ["点击这里阅读全文哦!"]
},
{
"selector": "#card-toc",
"text": ["文章太长?看看目录吧!", "这里是文章的大纲哦!"]
},
{
"selector": ".post-meta__tags a",
"text": ["要去看看 <span>{text}</span> 标签么?"]
},
{
"selector": ".article-categories a",
"text": ["要去看看 <span>{text}</span> 分类么?"]
},
{
"selector": ".post-reward .reward-button",
"text": ["主人最近在吃土呢,给他一些钱钱吧~", "要打赏我嘛?好期待啊~"]
},
{
"selector": "#post-comment",
"text": ["想要去评论些什么吗?", "觉得博客不错?快来留言和主人交流吧!"]
},
{
"selector": "#darkmode",
"text": ["眼睛累了吗?切换一下深色模式吧!", "要关灯了吗?"]
},
{
"selector": "#go-up",
"text": ["点它就可以回到顶部啦!", "嗖——的一下飞到上面去!"]
},
{
"selector": "#rightside_config",
"text": ["这里可以调整博客的设置哦!"]
},
{
"selector": ".copy-btn",
"text": ["代码可以直接点击复制哟。"]
},
{
"selector": "#footer-wrap a",
"text": ["去 <span>{text}</span> 逛逛吧。"]
},
{
"selector": "#waifu-tool-hitokoto",
"text": ["猜猜我要说些什么?", "我从青蛙王子那里听到了不少人生经验。"]
},
{
"selector": "#waifu-tool-express",
"text": ["要看看我的其他表情吗?", "变脸术!"]
},
{
"selector": "#waifu-tool-info",
"text": ["想要知道更多关于我的事么?", "你想深入了解我什么呢?"]
},
{
"selector": "#waifu-tool-quit",
"text": ["到了要说再见的时候了吗?", "呜呜 QAQ 后会有期……", "不要抛弃我呀……", "哼,你会后悔的!"]
}
],

  其中selector字段需要修改为Butterfly主题中对应元素的CSS选择器,text字段中的提示语可以根据需要进行自定义。

6.4 模型性能优化

  moc3模型由于复杂的物理模拟和动画系统,在Web端的渲染性能要求较高。为了保证流畅和避免卡顿,需要进行额外的渲染性能优化。

6.4.1 缓冲区优化

  在第4节引入的waifu-tips.js中,注入 Canvas 的逻辑为:<canvas id="live2d" width="800" height="800"></canvas>这表明 WebGL 计算的像素缓冲区大小为 800x800 像素,但 waifu.css 中仅设定为 300x300 像素显示,导致 GPU 每秒需要计算一个远大于实际显示需求的画布,造成严重的资源浪费。因此需要修改 Canvas 的宽高属性与 CSS 一致:

1
<canvas id="live2d" width="300" height="300"></canvas>

6.4.2 渲染层优化

  浏览器会将 Live2D 的 <canvas> 和博客的其他背景、文字放在同一个 Layer 中。Live2D 的高频刷新有概率引起页面滚动掉帧。解决办法是利用 CSS 的 3D 属性将 Live2D 提升到一个独立的 GPU 硬件加速图层,使其渲染独立于网页的其他元素。在 waifu.css 中找到 #waifu 选择器,添加以下属性:

1
2
3
4
5
#waifu {
/* 其他样式属性不变... */
will-change: transform; /* 提前告知浏览器该元素将频繁变化,启用 GPU 加速 */
transform: translateZ(0); /* 强制开启独立的 GPU 图层 */
}

6.4.3 加载优化

  由于 Live2D 脚本是直接注入在 bottom 的,它会和博客本身的文章内容、图片、其他脚本同时竞争加载资源。因此添加懒加载,等博客加载完成后再加载 Live2D。修改 inject.bottom 中的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 监听 window 的 load 事件
if (screen.width >= 768) {
window.addEventListener('load', () => {
const initTask = () => {
Promise.all([
loadExternalResource(config.path.cssPath, "css"),
loadExternalResource(config.path.live2dCorePath, "js"),
loadExternalResource(config.path.live2dSdkPath, "js"),
loadExternalResource(config.path.tipsJsPath, "js")
]).then(() => {
if (typeof initWidget !== "undefined") {
initWidget({
waifuPath: config.path.tipsJsonPath,
cdnPath: config.path.modelPath,
tools: config.tools,
dragEnable: config.drag.enable,
dragDirection: config.drag.direction,
switchType: config.switchType
});
}
});
};
if (window.requestIdleCallback) {
requestIdleCallback(initTask);
} else {
setTimeout(initTask, 500);
}
});
}
// 后续代码不变...

6.5 CDN 加速配置

  这里我总结了两种方案,第一种是通过jsDelivr CDN 加速github资源,另一种是使用服务器的对象存储服务(如阿里云OSS、腾讯云COS等)加速资源。

6.5.1 jsDelivr CDN 加速配置

  • 优点:免费、配置简单、全球加速、自动更新
  • 缺点:对于国内服务器访问速度受限、对大文件支持有限、DNS 污染风险

  首先在Live2d-Widget-v3项目中下载/Core/live2dcubismcore.js/live2d-sdk.js两个文件,添加到source/live2d/目录下。将live2d/下的所有资源推送到公共GitHub仓库。然后在_config.butterfly.yml中将路径修改为jsDelivr CDN地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
const cdnPath = "https://cdn.jsdelivr.net/gh/LuoTian001/live2d-widget-AIChat@main/";
const config = {
path: {
homePath: "/",
modelPath: cdnPath,
cssPath: cdnPath + "waifu.css",
tipsJsonPath: cdnPath + "waifu-tips.json",
tipsJsPath: cdnPath + "waifu-tips.js",
live2dCorePath: cdnPath + "Core/live2dcubismcore.js",
live2dSdkPath: cdnPath + "live2d-sdk.js"
},
# 此处省略其他配置项...
};

  如果使用自定义github仓库,cdnPath需替换为https://cdn.jsdelivr.net/gh/{用户名}/{仓库名}@{标签名}/

6.5.2 阿里云 OSS 加速配置

  • 优点:国内访问速度快、支持大文件、稳定可靠
  • 缺点:需要付费、配置相对复杂

  如果上述 CDN 配置不满足需求,可以利用国内云服务器厂商的对象存储服务。同样需准备相关资源文件(同6.5.1节)。以阿里云 OSS 为例,将模型资源上传到该存储空间中,然后获取该资源的公共访问 URL(该配置过程较为繁琐),修改 _config.butterfly.yml 中的路径配置为该 URL。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
const cdnPath = "https://{bucket-name}.{region}.aliyuncs.com/live2d/";
const config = {
path: {
homePath: "/",
modelPath: cdnPath,
cssPath: cdnPath + "waifu.css",
tipsJsonPath: cdnPath + "waifu-tips.json",
tipsJsPath: cdnPath + "waifu-tips.js",
live2dCorePath: cdnPath + "Core/live2dcubismcore.js",
live2dSdkPath: cdnPath + "live2d-sdk.js"
},
# 此处省略其他配置项...
};

  其中{bucket-name}替换为你的OSS存储空间名称,{region}替换为你的OSS所在地域。完成配置后重新构建博客,模型资源就会通过阿里云 OSS 加速加载了。阿里云也有基于OSS存储的CDN加速服务,可以选择进一步优化。其他云服务商的对象存储服务(如腾讯云 COS、AWS S3 等)配置方法类似,只需替换为对应的公共访问 URL 即可。