vue3中使用live2D
# 概述
本文将介绍如何在vue3
项目中使用Live2D。
# Live2D 介绍
# Live2D 是什么
Live2D 是一种用于将二维图像转化为可动画三维模型的技术,主要应用于游戏、虚拟角色和互动应用中。它允许开发者通过对静态图像进行分层和建模,使角色在不同角度下能够进行流畅的动作和表情变化。
# Live2D 主要特点
动态表现:Live2D 可以让角色在不改变原始图像的情况下,实现多种动作和表情,例如眨眼、微笑、转头等。
用户交互:它可以与用户的输入进行互动,比如鼠标移动或触摸屏幕时,角色会作出相应的反馈。
应用广泛:被广泛应用于手机游戏、动画、虚拟直播、社交软件等领域。
易于使用:通过 Live2D 提供的工具(如 Live2D Cubism),艺术家可以方便地创建和编辑模型,无需深入的编程知识。
# Live2D 效果
# vue3 中使用 Live2D
如果前端项目是用vite
搭建,则需要在index.html
中引入live2d.min.js
库,因为在 vue 组件中引入会报错。
# 封装 Live 组件
template
template
部分如下:
<template>
<div
:class="{
'vue-live2d': true,
'vue-live2d-on-left': true,
// 'vue-live2d-on-right': direction === 'right',
}"
:style="{
width: `300px`,
height: `300px`,
position: 'absolute',
left: '10px',
bottom: '20px',
}"
@mouseover="openLive2dTool"
@mouseout="closeLive2dTool"
>
<div
v-show="true"
v-html="data.tipText"
:class="{
'vue-live2d-tip': true,
'vue-live2d-tip-on-top': true,
// 'vue-live2d-tip-on-bottom': tipPosition === 'bottom',
}"
></div>
<canvas
:id="customId"
v-show="mainShow"
:class="{
'vue-live2d-main': true,
'vue-live2d-main-on-left': true,
// 'vue-live2d-main-on-right': direction === 'right',
}"
width="300"
height="300"
>
</canvas>
<div v-show="toolShow" class="vue-live2d-tool">
<span
v-for="(tool, index) in tools"
:key="index"
:class="tool.className"
v-html="tool.svg"
@click="tool.click"
/>
</div>
<div
v-show="toggleShow"
@click="openLive2dMain"
:class="{
'vue-live2d-toggle': true,
'vue-live2d-toggle-on-left': true,
// 'vue-live2d-toggle-on-right': direction === 'right',
}"
>
<span>Kanban girl</span>
</div>
</div>
</template>
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
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
template
部分主要是定义 UI 部分,以及绑定界面点击和hove
的事件,Live2d
的模型是在 canvas 中绘制的。
核心逻辑
核心逻辑主要就是加载Live2D
的模型以及定义事件,其实现如下:
<script setup>
import { onMounted, nextTick, ref, computed } from "vue";
import tips from "./options/tips";
// const model = ["Potion-Maker/Pio", "school-2017-costume-yellow"];
const model = ["ShizukuTalk/shizuku-48", "default"];
const apiPath = "https://evgo2017.com/api/live2d-static-api/indexes";
let [modelPath, modelTexturesId] = model;
const customId = "vue-live2d-main";
let messageTimer = null;
const data = ref({
containerDisplay: {
tip: false,
main: true,
tool: false,
toggle: false,
},
tipText: "vue-live2d Kanban girl",
modelTexturesId: modelTexturesId,
});
const changeLive2dSize = () => {
document.querySelector(
`#${customId}`
).outerHTML = `<canvas id=${customId} width="300" height="300" class="vue-live2d-main"></canvas>`;
loadModel();
};
const loadModel = () => {
window.loadlive2d(
customId,
`${apiPath}/${modelPath}/${data.value.modelTexturesId}.json`
);
console.log(
`Live2D 模型 ${modelPath},服装 ${data.value.modelTexturesId} 加载完成`
);
};
const loadRandModel = () => {
http({
url: `${apiPath}/models.json`,
success: (data) => {
const models = data.filter(({ modelPath: i }) => i !== modelPath);
const { modelPath: j, modelIntroduce } =
models[Math.floor(Math.random() * models.length)];
modelPath = j;
showMessage(`${modelIntroduce}`, 4000);
loadRandTextures(true);
},
});
};
const loadRandTextures = (isAfterRandModel = false) => {
http({
url: `${apiPath}/${modelPath}/textures.json`,
success: (resp) => {
const modelTexturesIds = resp.filter(
(modelTexturesId) => modelTexturesId !== data.value.modelTexturesId
);
data.value.modelTexturesId =
modelTexturesIds[Math.floor(Math.random() * modelTexturesIds.length)];
loadModel();
if (!isAfterRandModel) {
showMessage("我的新衣服好看嘛?", 4000);
}
},
});
};
const showMessage = (msg = "", timeout = 6000) => {
if (messageTimer) {
clearTimeout(messageTimer);
messageTimer = null;
} else {
data.value.containerDisplay.tip = true;
}
data.value.tipText = msg;
messageTimer = setTimeout(() => {
data.value.containerDisplay.tip = false;
messageTimer = null;
}, timeout);
};
const takePhoto = () => {
showMessage("照好了嘛,留个纪念吖~");
window.Live2D.captureName = "photo.png";
window.Live2D.captureFrame = true;
};
const showHitokoto = () => {
http({
url: "https://v1.hitokoto.cn",
success: ({ hitokoto, id, creator, from }) => {
showMessage(
`${hitokoto} <br> - by <a href="https://hitokoto.cn?id=${id}">${creator}</a> from 《${from} 》`
);
},
});
};
const closeLive2dMain = () => {
data.value.containerDisplay.main = false;
};
const openLive2dMain = () => {
data.value.containerDisplay.main = true;
};
const closeLive2dTool = () => {
data.value.containerDisplay.tool = false;
};
const openLive2dTool = () => {
data.value.containerDisplay.tool = true;
};
const loadEvent = () => {
for (const event in tips) {
for (const { selector, texts } of tips[event]) {
const dom =
selector === "document" ? document : document.querySelector(selector);
if (dom == null) {
continue;
}
dom.addEventListener(event, () => {
const msg = texts[Math.floor(Math.random() * texts.length)];
showMessage(msg, 2000);
});
}
}
};
const http = ({ url, success }) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 || xhr.status < 300 || xhr.status === 304) {
success && xhr.response && success(JSON.parse(xhr.response));
} else {
console.error(xhr);
}
}
};
xhr.open("GET", url);
xhr.send(null);
};
nextTick(() => {
loadEvent();
});
onMounted(() => {
loadModel();
});
defineProps({
tips: {
default: () => tips,
type: Object,
},
width: {
default: 0,
type: Number,
},
height: {
default: 0,
type: Number,
},
size: {
default: 255,
type: Number,
},
});
const tipShow = computed(() => {
return mainShow && data.value.containerDisplay.tip;
});
const mainShow = computed(() => {
return data.value.containerDisplay.main;
});
const toolShow = computed(() => {
return mainShow && data.value.containerDisplay.tool;
});
const toggleShow = computed(() => {
return !mainShow;
});
const tools = ref([
{
className: "custom-fa-comment",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M256 32C114.6 32 0 125.1 0 240c0 49.6 21.4 95 57 130.7C44.5 421.1 2.7 466 2.2 466.5c-2.2 2.3-2.8 5.7-1.5 8.7S4.8 480 8 480c66.3 0 116-31.8 140.6-51.4 32.7 12.3 69 19.4 107.4 19.4 141.4 0 256-93.1 256-208S397.4 32 256 32z"/></svg>',
click: showHitokoto,
},
{
className: "custom-fa-user-circle",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z"/></svg>',
click: loadRandModel,
},
{
className: "custom-fa-street-view",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M367.9 329.76c-4.62 5.3-9.78 10.1-15.9 13.65v22.94c66.52 9.34 112 28.05 112 49.65 0 30.93-93.12 56-208 56S48 446.93 48 416c0-21.6 45.48-40.3 112-49.65v-22.94c-6.12-3.55-11.28-8.35-15.9-13.65C58.87 345.34 0 378.05 0 416c0 53.02 114.62 96 256 96s256-42.98 256-96c0-37.95-58.87-70.66-144.1-86.24zM256 128c35.35 0 64-28.65 64-64S291.35 0 256 0s-64 28.65-64 64 28.65 64 64 64zm-64 192v96c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32v-96c17.67 0 32-14.33 32-32v-96c0-26.51-21.49-48-48-48h-11.8c-11.07 5.03-23.26 8-36.2 8s-25.13-2.97-36.2-8H208c-26.51 0-48 21.49-48 48v96c0 17.67 14.33 32 32 32z"/></svg>',
click: loadRandTextures,
},
{
className: "custom-fa-camera-retro",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M48 32C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48zm0 32h106c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H38c-3.3 0-6-2.7-6-6V80c0-8.8 7.2-16 16-16zm426 96H38c-3.3 0-6-2.7-6-6v-36c0-3.3 2.7-6 6-6h138l30.2-45.3c1.1-1.7 3-2.7 5-2.7H464c8.8 0 16 7.2 16 16v74c0 3.3-2.7 6-6 6zM256 424c-66.2 0-120-53.8-120-120s53.8-120 120-120 120 53.8 120 120-53.8 120-120 120zm0-208c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88zm-48 104c-8.8 0-16-7.2-16-16 0-35.3 28.7-64 64-64 8.8 0 16 7.2 16 16s-7.2 16-16 16c-17.6 0-32 14.4-32 32 0 8.8-7.2 16-16 16z"/></svg>',
click: takePhoto,
},
{
className: "custom-fa-info-circle",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/></svg>',
click: () => {},
},
{
className: "custom-fa-times",
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-40 -40 432 592" fill="currentColor" height="20px" width="20px"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"/></svg>',
click: closeLive2dMain,
},
]);
</script>
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Demo地址
Demo地址和具体示例可参考:https://github.com/Jinuss/maps (opens new window)
编辑 (opens new window)
上次更新: 2024/10/09, 09:39:25