代码修改于 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>