代码修改于 https://www.fghrsh.net/post/123.html
核心依赖 live2d.min.js 可以从 https://github.com/stevenjoezhang/live2d-widget下载,嫌速度慢的话,gitee上也有。
l i v e 2 d . t i p s . j s live2d.tips.js live2d.tips.js(对话框逻辑)
/** live2d 加载类 */
class Live2d_tips {
constructor(config) {
let { modelSrc, modelId, modelSite, actionTime, actionText, musicListId } =
config;
this.modelSrc = modelSrc;
this.width = modelSite.width;
this.height = modelSite.height;
this.hOffset = modelSite.hOffset;
this.vOffset = modelSite.vOffset;
this.actionTime = actionTime;
this.actionText = actionText;
this.modelId = modelId;
this.musicListId = musicListId;
this.modelList = null;
if (modelSrc === null && modelId === undefined) {
throw new Error("ModelID and ModelSrc 为空, 必须填写其一.");
}
switch (modelId) {
case 0:
case 1:
case 2:
case 3:
this.executor();
break;
default:
throw new Error("ModelId 不符合规则 (id: 0 ~ 3)");
}
}
executor() {
this.loadWidget();
}
loadWidget() {
if (this.modelSrc) {
console.log(`location found ${this.modelSrc}`);
} else {
console.log("network to loading");
}
document.body.insertAdjacentHTML(
"beforebegin",
`
<div id="live2d-widget">
<div class="live2d-tips"></div>
<canvas id="live2d" width="830" height="900"></canvas>
<div class="live2d-tools">
<span class="fa fa-lg fa-camera-retro"></span>
<span class="fa fa-lg fa-music"></span>
<span class="fa fa-lg fa-info-circle"></span>
<span class="fa fa-lg fa-times"></span>
</div>
</div>
<div class="live2d-toggle">看板娘</div>
`
);
this.setSite();
let userAction = false,
textTimer,
actionTimer;
(function welcomeWidget() {
const now = new Date().getHours();
let text;
if (location.pathname !== "/") {
if (now >= 5 && now < 7) {
text = `早上好,我的主人`;
} else if (now >= 7 && now < 11) {
text = `上午过的舒服吗?<span>不要太劳累哦。</span>`;
} else if (now >= 11 && now < 13) {
text = `该吃午饭了,休息休息吧~`;
} else if (now >= 13 && now < 15) {
text = `工作时间,喝杯茶<span>「提提神」</span>`;
} else if (now >= 15 && now < 17) {
text = `完成的差不多了,赶紧收工吧!`;
} else if (now >= 17 && now < 19) {
text = `吃晚饭咯,一天过得真快呀。`;
} else if (now >= 19 && now < 21) {
text = `晚上好,今天过得这么样?`;
} else if (now >= 21 && now < 23) {
text = `早点睡吧,对身体好,明天又是<span>「元气满满」</span>的一天`;
} else {
text = `快点睡觉!再不睡觉,我生气了哦。`;
}
}
showMessage(text, 7000, 8);
})();
window.addEventListener("mousemove", () => (userAction = true));
window.addEventListener("keydown", () => (userAction = true));
setInterval(() => {
if (userAction) {
userAction = false;
clearInterval(actionTimer);
actionTimer = null;
} else if (!actionTimer) {
actionTimer = setInterval(() => {
showMessage(this.actionText, 4000, 9);
}, this.actionTime);
}
}, 2000);
let musicArr = [],
audio = null;
document
.querySelector("#live2d-widget #live2d")
.addEventListener("click", () => {
hitokoto("动画");
});
document
.querySelector("#live2d-widget #live2d")
.addEventListener("mouseover", () => {
let tools = document.querySelector("#live2d-widget .live2d-tools");
tools.classList.add("active");
});
document
.querySelector("#live2d-widget .live2d-tools")
.addEventListener("mouseover", function () {
this.classList.add("active");
});
document
.querySelector("#live2d-widget #live2d")
.addEventListener("mouseout", () => {
let tools = document.querySelector("#live2d-widget .live2d-tools");
tools.classList.remove("active");
});
document
.querySelector(".live2d-tools .fa-camera-retro")
.addEventListener("click", () => {
showMessage(`卡哇伊,合影留念吧~`, 6000, 9);
Live2D.captureName = "photo.png";
Live2D.captureFrame = true;
});
document
.querySelector(".live2d-tools .fa-music")
.addEventListener("click", () => {
// 调用hitokoto提供的api
fetch163Playlist(this.musicListId)
.then((data) => {
for (const music in data) {
musicArr.push(data[music].url);
}
return musicArr;
})
.then((music) => {
if (audio) {
audio.load();
}
audio = new Audio(randSection(music));
audio.play();
})
.catch(console.error);
});
document
.querySelector(".live2d-tools .fa-info-circle")
.addEventListener("click", () => {
showMessage(`Go go go~`, 6000, 9);
open("https://www.live2d.com/en/");
});
document
.querySelector(".live2d-tools .fa-times")
.addEventListener("click", function () {
let live2d = document.querySelector("#live2d-widget");
live2d.style.bottom = "-500px";
setTimeout(() => {
live2d.style.display = "none";
document.querySelector(".live2d-toggle").classList.add("active");
}, 2000);
});
document.querySelector(".live2d-toggle").addEventListener("click", () => {
document.querySelector(".live2d-toggle").classList.remove("active");
let live2d = document.querySelector("#live2d-widget");
console.log(this.vOffset);
live2d.style.display = "block";
setTimeout(() => {
live2d.style.bottom = this.vOffset + "px";
}, 0);
});
function hitokoto(typeName) {
let type;
switch (typeName) {
case "动画":
type = "a";
break;
case "漫画":
type = "b";
break;
case "游戏":
type = "c";
break;
default:
typeName = "动画";
type = "a";
break;
}
fetch(`https://v1.hitokoto.cn/?c=${type}`)
.then((response) => response.json())
.then((result) => {
const text = `来自${typeName} <span>「${result.from}」</span> 的留言`;
showMessage(result.hitokoto, 4000, 9);
setTimeout(() => {
showMessage(text, 4000, 9);
}, 4000);
});
}
const loadJSON = async (CDN) => {
const response = await fetch(`${CDN}model_list.json`);
this.modelList = await response.json();
};
const loadModel = async (
CDN = "https://cdn.jsdelivr.net/gh/fghrsh/live2d_api/"
) => {
if (!this.modelSrc) {
await loadJSON(CDN);
let target = this.modelList.models[this.modelId];
loadlive2d("live2d", `${CDN}model/${target}/index.json`);
} else {
loadlive2d("live2d", this.modelSrc);
}
};
function randSection(obj) {
return Array.isArray(obj)
? obj[Math.floor(Math.random() * obj.length)]
: obj;
}
function showMessage(text, timeout, protery) {
if (
!text ||
(sessionStorage.getItem("tips-text") &&
sessionStorage.getItem("tips-text") > protery)
)
return;
if (textTimer) {
clearTimeout(textTimer);
textTimer = null;
}
sessionStorage.setItem("tips-text", protery);
let tips = document.querySelector("#live2d-widget .live2d-tips");
let tip = randSection(text);
tips.innerHTML = tip;
tips.classList.add("active");
textTimer = setTimeout(() => {
tips.classList.remove("active");
sessionStorage.removeItem("tips-text");
}, timeout);
}
const devtools = () => {};
console.log("%c", devtools);
devtools.toString = () => {
showMessage(
`Live2d 有官网文档哦 ~ 请访问 <span>「https://www.live2d.com/en/」</span>`,
4000,
9
);
};
window.addEventListener("copy", () => {
showMessage(`你想获得力量吗?`, 4000, 9);
});
window.addEventListener("visibilitychange", () => {
if (!document.hidden) showMessage("你还好吗,担心死你了~", 4000, 9);
});
loadModel();
}
setSite() {
let live2d = document.querySelector("#live2d-widget");
setTimeout(() => {
live2d.style.bottom = this.vOffset + "px";
live2d.style.left = this.hOffset + "px";
live2d.style.width = this.width + "px";
live2d.style.height = this.height + "px";
}, 0);
}
}
l o a d . j s load.js load.js(加载标签)
/** 异步加载标签类 */
export default class Autoload {
constructor() {
return Promise.all([
this.loadExtendResource("./live2d.min.js", "js"),
this.loadExtendResource("./live2d.music.js", "js"),
this.loadExtendResource("./live2d.tips.js", "js"),
this.loadExtendResource("./demo.css", "css"),
]);
}
loadExtendResource(url, type) {
return new Promise((resolve, reject) => {
let tag;
switch (type) {
case "css":
tag = document.createElement("link");
tag.href = url;
tag.rel = "stylesheet";
break;
case "js":
tag = document.createElement("script");
tag.src = url;
break;
}
if (tag) {
tag.onload = () => resolve(url);
tag.onerror = () => reject(url);
document.head.appendChild(tag);
}
});
}
}
l i v e 2 d . m u s i c . j s live2d.music.js live2d.music.js(获取音乐)
/** 获取歌单数据,一言官网提供的示例 https://developer.hitokoto.cn/sentence/demo */
// 获取歌单列表数据
function fetch163Playlist(playlistId) {
return new Promise((ok, err) => {
fetch(`https://v1.hitokoto.cn/nm/playlist/${playlistId}`)
.then((response) => response.json())
.then((data) => {
const arr = [];
data.playlist.tracks.map(function (value) {
arr.push(value.id);
});
return arr;
})
.then(fetch163Songs)
.then(ok)
.catch(err);
});
}
// 获取歌曲数据
function fetch163Songs(Ids) {
return new Promise(function (ok, err) {
let ids;
switch (typeof Ids) {
case "number":
ids = [Ids];
break;
case "object":
if (!Array.isArray(Ids)) {
err(new Error("Please enter array or number"));
return;
}
ids = Ids;
break;
default:
err(new Error("Please enter array or number"));
return;
break;
}
fetch(
`https://v1.hitokoto.cn/nm/summary/${ids.join(
","
)}?lyric=true&common=true`
)
.then((response) => response.json())
.then((data) => {
var songs = [];
data.songs.map(function (song) {
songs.push({
name: song.name,
url: song.url,
artist: song.artists.join("/"),
album: song.album.name,
pic: song.album.picture,
lrc: song.lyric,
});
});
return songs;
})
.then(ok)
.catch(err);
});
}
d e m o . c s s demo.css demo.css(看板娘样式表)
/** demo.css */
div#live2d-widget {
position: fixed;
transition: 3s;
}
div#live2d-widget canvas#live2d {
width: 300px;
height: 300px;
}
div.live2d-toggle {
margin-left: -100px;
position: fixed;
bottom: 66px;
background: #fa0;
border-radius: 5px;
color: #fff;
font-size: 12px;
padding: 5px;
transition: 1s;
width: 60px;
writing-mode: vertical-rl;
}
div.live2d-toggle.active {
margin-left: -50px;
}
div.live2d-toggle.active:hover {
margin-left: -30px;
}
div#live2d-widget div.live2d-tips {
position: absolute;
width: 250px;
height: 100px;
background: #ffbcd8;
border-radius: 5px;
border: 1px solid #ff8cc8;
color: #000;
filter: blur(5px);
box-sizing: border-box;
padding: 10px;
left: 50%;
top: -80px;
transform: translateX(-50%);
animation: tipsAnim 25s ease-in-out 5s infinite;
transition: opacity 1s;
opacity: 0;
box-shadow: 0 0 10px 3px rgba(255, 188, 216, 0.7);
font-family: "黑体";
font-size: 14px;
z-index: -1;
}
div#live2d-widget div.live2d-tips.active {
transition: opacity 0.2s;
opacity: 1;
filter: none;
}
div#live2d-widget div.live2d-tips > span {
color: #29c5ff;
}
div#live2d-widget div.live2d-tools {
position: absolute;
bottom: 50%;
transform: translate(0, 50%);
right: 30px;
opacity: 0;
transition: opacity 1s;
}
div#live2d-widget div.live2d-tools.active {
opacity: 1;
transition: opacity 0.5s;
}
div#live2d-widget div.live2d-tools > span {
line-height: 30px;
display: block;
}
@keyframes tipsAnim {
2% {
transform: translateX(-50%) translate(0.5px, -1.5px) rotate(-0.5deg);
}
4% {
transform: translateX(-50%) translate(0.5px, 1.5px) rotate(1.5deg);
}
6% {
transform: translateX(-50%) translate(1.5px, 1.5px) rotate(1.5deg);
}
8% {
transform: translateX(-50%) translate(2.5px, 1.5px) rotate(0.5deg);
}
10% {
transform: translateX(-50%) translate(0.5px, 2.5px) rotate(0.5deg);
}
12% {
transform: translateX(-50%) translate(1.5px, 1.5px) rotate(0.5deg);
}
14% {
transform: translateX(-50%) translate(0.5px, 0.5px) rotate(0.5deg);
}
16% {
transform: translateX(-50%) translate(-1.5px, -0.5px) rotate(1.5deg);
}
18% {
transform: translateX(-50%) translate(0.5px, 0.5px) rotate(1.5deg);
}
20% {
transform: translateX(-50%) translate(2.5px, 2.5px) rotate(1.5deg);
}
22% {
transform: translateX(-50%) translate(0.5px, -1.5px) rotate(1.5deg);
}
24% {
transform: translateX(-50%) translate(-1.5px, 1.5px) rotate(-0.5deg);
}
26% {
transform: translateX(-50%) translate(1.5px, 0.5px) rotate(1.5deg);
}
28% {
transform: translateX(-50%) translate(-0.5px, -0.5px) rotate(-0.5deg);
}
30% {
transform: translateX(-50%) translate(1.5px, -0.5px) rotate(-0.5deg);
}
32% {
transform: translateX(-50%) translate(2.5px, -1.5px) rotate(1.5deg);
}
34% {
transform: translateX(-50%) translate(2.5px, 2.5px) rotate(-0.5deg);
}
36% {
transform: translateX(-50%) translate(0.5px, -1.5px) rotate(0.5deg);
}
38% {
transform: translateX(-50%) translate(2.5px, -0.5px) rotate(-0.5deg);
}
40% {
transform: translateX(-50%) translate(-0.5px, 2.5px) rotate(0.5deg);
}
42% {
transform: translateX(-50%) translate(-1.5px, 2.5px) rotate(0.5deg);
}
44% {
transform: translateX(-50%) translate(-1.5px, 1.5px) rotate(0.5deg);
}
46% {
transform: translateX(-50%) translate(1.5px, -0.5px) rotate(-0.5deg);
}
48% {
transform: translateX(-50%) translate(2.5px, -0.5px) rotate(0.5deg);
}
50% {
transform: translateX(-50%) translate(-1.5px, 1.5px) rotate(0.5deg);
}
52% {
transform: translateX(-50%) translate(-0.5px, 1.5px) rotate(0.5deg);
}
54% {
transform: translateX(-50%) translate(-1.5px, 1.5px) rotate(0.5deg);
}
56% {
transform: translateX(-50%) translate(0.5px, 2.5px) rotate(1.5deg);
}
58% {
transform: translateX(-50%) translate(2.5px, 2.5px) rotate(0.5deg);
}
60% {
transform: translateX(-50%) translate(2.5px, -1.5px) rotate(1.5deg);
}
62% {
transform: translateX(-50%) translate(-1.5px, 0.5px) rotate(1.5deg);
}
64% {
transform: translateX(-50%) translate(-1.5px, 1.5px) rotate(1.5deg);
}
66% {
transform: translateX(-50%) translate(0.5px, 2.5px) rotate(1.5deg);
}
68% {
transform: translateX(-50%) translate(2.5px, -1.5px) rotate(1.5deg);
}
70% {
transform: translateX(-50%) translate(2.5px, 2.5px) rotate(0.5deg);
}
72% {
transform: translateX(-50%) translate(-0.5px, -1.5px) rotate(1.5deg);
}
74% {
transform: translateX(-50%) translate(-1.5px, 2.5px) rotate(1.5deg);
}
76% {
transform: translateX(-50%) translate(-1.5px, 2.5px) rotate(1.5deg);
}
78% {
transform: translateX(-50%) translate(-1.5px, 2.5px) rotate(0.5deg);
}
80% {
transform: translateX(-50%) translate(-1.5px, 0.5px) rotate(-0.5deg);
}
82% {
transform: translateX(-50%) translate(-1.5px, 0.5px) rotate(-0.5deg);
}
84% {
transform: translateX(-50%) translate(-0.5px, 0.5px) rotate(1.5deg);
}
86% {
transform: translateX(-50%) translate(2.5px, 1.5px) rotate(0.5deg);
}
88% {
transform: translateX(-50%) translate(-1.5px, 0.5px) rotate(1.5deg);
}
90% {
transform: translateX(-50%) translate(-1.5px, -0.5px) rotate(-0.5deg);
}
92% {
transform: translateX(-50%) translate(-1.5px, -1.5px) rotate(1.5deg);
}
94% {
transform: translateX(-50%) translate(0.5px, 0.5px) rotate(-0.5deg);
}
96% {
transform: translateX(-50%) translate(2.5px, -0.5px) rotate(-0.5deg);
}
98% {
transform: translateX(-50%) translate(-1.5px, -1.5px) rotate(-0.5deg);
}
0%,
100% {
transform: translateX(-50%) translate(0, 0) rotate(0);
}
}
d e m o . h t m l demo.html demo.html(用于测试的文档)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css"
/>
<title>My test</title>
</head>
<body>
<script type="module">
import Autoload from "./load.js";
new Autoload().then((value) => {
new Live2d_tips({
modelId: 2,
modelSrc: null,
modelSite: {
width: 300,
height: 300,
hOffset: 0,
vOffset: 0,
},
musicListId: 6752075988,
actionTime: 5000,
actionText: ["你为啥不理我了", "又是颓废的一天", "Live2d 启动!"],
});
});
</script>
</body>
</html>