Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

    • Git使用手册
    • craco-less使用问题
    • npm常用命令
    • 绘制多彩的三角形
    • 开发chrome扩展插件
    • npm的作用域介绍
    • Django开发基础介绍
    • Django开发前端篇
    • Django Web开发接口定义
    • npm packageJson属性详解
    • vue3中使用live2D
      • 概述
      • Live2D 介绍
        • Live2D 是什么
        • Live2D 主要特点
        • Live2D 效果
      • vue3 中使用 Live2D
        • 封装 Live 组件
        • Demo地址
    • Worker加载外部文件
    • vite配置打包分类文件
    • 微前端基础知识入门篇一
    • yaml语言教程
    • 微前端基础知识入门篇(二)
  • GitHub技巧

  • SVG

  • 技术
  • 技术文档
东流
2024-10-09
目录

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

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

# Demo地址

Demo地址和具体示例可参考:https://github.com/Jinuss/maps (opens new window)

编辑 (opens new window)
上次更新: 2024/10/09, 09:39:25
npm packageJson属性详解
Worker加载外部文件

← npm packageJson属性详解 Worker加载外部文件→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式