From abb832a9ea3f006b7d8b9c0567bba99c5603b426 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Thu, 20 Oct 2022 17:28:52 +0800 Subject: [PATCH] Inline SVG icon --- .gitignore | 2 + package.json | 30 +++++ rollup.config.js | 30 +++++ src/waifu-tips.js | 333 ++++++++++++++++++++++++++++++++++++++++++++++ waifu.css | 18 ++- 5 files changed, 406 insertions(+), 7 deletions(-) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 rollup.config.js create mode 100755 src/waifu-tips.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15813be --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +package-lock.json +node_modules/ diff --git a/package.json b/package.json new file mode 100644 index 0000000..65a2768 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "live2d-widget", + "version": "0.9.0", + "description": "Live2D widget for web pages", + "main": "autoload.js", + "type": "module", + "scripts": { + "build": "rollup -c rollup.config.js -f iife | terser > waifu-tips.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/stevenjoezhang/live2d-widget.git" + }, + "keywords": [ + "Live2d" + ], + "author": "stevenjoezhang ", + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/stevenjoezhang/live2d-widget/issues" + }, + "homepage": "https://github.com/stevenjoezhang/live2d-widget#readme", + "devDependencies": { + "@fortawesome/fontawesome-free": "^6.2.0", + "@rollup/plugin-node-resolve": "^15.0.0", + "@rollup/pluginutils": "^5.0.1", + "rollup": "^3.2.3", + "terser": "^5.15.1" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..70e9786 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,30 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import { createFilter } from "@rollup/pluginutils"; + +function string(opts = {}) { + if (!opts.include) { + throw Error("include option should be specified"); + } + + const filter = createFilter(opts.include, opts.exclude); + + return { + name: "string", + + transform(code, id) { + if (filter(id)) { + return { + code: `export default ${JSON.stringify(code)};`, + map: { mappings: "" } + }; + } + } + }; +} + +export default { + input: 'src/waifu-tips.js', + plugins: [nodeResolve(), string({ + include: "**/*.svg", + })] +}; diff --git a/src/waifu-tips.js b/src/waifu-tips.js new file mode 100755 index 0000000..2e3d911 --- /dev/null +++ b/src/waifu-tips.js @@ -0,0 +1,333 @@ +/* + * Live2D Widget + * https://github.com/stevenjoezhang/live2d-widget + */ + +import fa_comment from "@fortawesome/fontawesome-free/svgs/solid/comment.svg"; +import fa_paper_plane from "@fortawesome/fontawesome-free/svgs/solid/paper-plane.svg"; +import fa_user_circle from "@fortawesome/fontawesome-free/svgs/solid/circle-user.svg"; +import fa_street_view from "@fortawesome/fontawesome-free/svgs/solid/street-view.svg"; +import fa_camera_retro from "@fortawesome/fontawesome-free/svgs/solid/camera-retro.svg"; +import fa_info_circle from "@fortawesome/fontawesome-free/svgs/solid/circle-info.svg"; +import fa_xmark from "@fortawesome/fontawesome-free/svgs/solid/xmark.svg"; + +function loadWidget(config) { + let { apiPath, cdnPath } = config; + let useCDN = false, modelList; + if (typeof cdnPath === "string") { + useCDN = true; + if (!cdnPath.endsWith("/")) cdnPath += "/"; + } else if (typeof apiPath === "string") { + if (!apiPath.endsWith("/")) apiPath += "/"; + } else { + console.error("Invalid initWidget argument!"); + return; + } + localStorage.removeItem("waifu-display"); + sessionStorage.removeItem("waifu-text"); + document.body.insertAdjacentHTML("beforeend", `
+
+ +
+
`); + // https://stackoverflow.com/questions/24148403/trigger-css-transition-on-appended-element + setTimeout(() => { + document.getElementById("waifu").style.bottom = 0; + }, 0); + + function randomSelection(obj) { + return Array.isArray(obj) ? obj[Math.floor(Math.random() * obj.length)] : obj; + } + // 检测用户活动状态,并在空闲时显示消息 + let userAction = false, + userActionTimer, + messageTimer, + messageArray = ["好久不见,日子过得好快呢……", "大坏蛋!你都多久没理人家了呀,嘤嘤嘤~", "嗨~快来逗我玩吧!", "拿小拳拳锤你胸口!", "记得把小家加入 Adblock 白名单哦!"]; + window.addEventListener("mousemove", () => userAction = true); + window.addEventListener("keydown", () => userAction = true); + setInterval(() => { + if (userAction) { + userAction = false; + clearInterval(userActionTimer); + userActionTimer = null; + } else if (!userActionTimer) { + userActionTimer = setInterval(() => { + showMessage(randomSelection(messageArray), 6000, 9); + }, 20000); + } + }, 1000); + + (function registerEventListener() { + const tools = { + "hitokoto": { + icon: fa_comment, + callback: showHitokoto + }, + "asteroids": { + icon: fa_paper_plane, + callback: () => { + if (window.Asteroids) { + if (!window.ASTEROIDSPLAYERS) window.ASTEROIDSPLAYERS = []; + window.ASTEROIDSPLAYERS.push(new Asteroids()); + } else { + const script = document.createElement("script"); + script.src = "https://fastly.jsdelivr.net/gh/stevenjoezhang/asteroids/asteroids.js"; + document.head.appendChild(script); + } + } + }, + "switch-model": { + icon: fa_user_circle, + callback: loadOtherModel + }, + "switch-texture": { + icon: fa_street_view, + callback: loadRandModel + }, + "photo": { + icon: fa_camera_retro, + callback: () => { + showMessage("照好了嘛,是不是很可爱呢?", 6000, 9); + Live2D.captureName = "photo.png"; + Live2D.captureFrame = true; + } + }, + "info": { + icon: fa_info_circle, + callback: () => { + open("https://github.com/stevenjoezhang/live2d-widget"); + } + }, + "quit": { + icon: fa_xmark, + callback: () => { + localStorage.setItem("waifu-display", Date.now()); + showMessage("愿你有一天能与重要的人重逢。", 2000, 11); + document.getElementById("waifu").style.bottom = "-500px"; + setTimeout(() => { + document.getElementById("waifu").style.display = "none"; + document.getElementById("waifu-toggle").classList.add("waifu-toggle-active"); + }, 3000); + } + } + }; + if (!Array.isArray(config.tools)) { + config.tools = Object.keys(tools); + } + for (let tool of config.tools) { + if (tools[tool]) { + const { icon, callback } = tools[tool]; + document.getElementById("waifu-tool").insertAdjacentHTML("beforeend", `${icon}`); + document.getElementById(`waifu-tool-${tool}`).addEventListener("click", callback); + } + } + + const devtools = () => {}; + console.log("%c", devtools); + devtools.toString = () => { + showMessage("哈哈,你打开了控制台,是想要看看我的小秘密吗?", 6000, 9); + }; + window.addEventListener("copy", () => { + showMessage("你都复制了些什么呀,转载要记得加上出处哦!", 6000, 9); + }); + window.addEventListener("visibilitychange", () => { + if (!document.hidden) showMessage("哇,你终于回来了~", 6000, 9); + }); + })(); + + function welcomeMessage() { + const message = `欢迎阅读「${document.title.split(" - ")[0]}」`; + let text; + if (location.pathname === "/") { // 如果是主页 + const now = new Date().getHours(); + if (now > 5 && now <= 7) text = "早上好!一日之计在于晨,美好的一天就要开始了。"; + else if (now > 7 && now <= 11) text = "上午好!工作顺利嘛,不要久坐,多起来走动走动哦!"; + else if (now > 11 && now <= 13) text = "中午了,工作了一个上午,现在是午餐时间!"; + else if (now > 13 && now <= 17) text = "午后很容易犯困呢,今天的运动目标完成了吗?"; + else if (now > 17 && now <= 19) text = "傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~"; + else if (now > 19 && now <= 21) text = "晚上好,今天过得怎么样?"; + else if (now > 21 && now <= 23) text = ["已经这么晚了呀,早点休息吧,晚安~", "深夜时要爱护眼睛呀!"]; + else text = "你是夜猫子呀?这么晚还不睡觉,明天起的来嘛?"; + return text; + } else if (document.referrer !== "") { + const referrer = new URL(document.referrer), + domain = referrer.hostname.split(".")[1]; + const domains = { + "baidu": "百度", + "so": "360搜索", + "google": "谷歌搜索" + }; + if (location.hostname === referrer.hostname) return message; + + if (domain in domains) text = domains[domain]; + else text = referrer.hostname; + return `Hello!来自 ${text} 的朋友
${message}`;; + } else { + return message; + } + }; + showMessage(welcomeMessage(), 7000, 8); + + function showHitokoto() { + // 增加 hitokoto.cn 的 API + fetch("https://v1.hitokoto.cn") + .then(response => response.json()) + .then(result => { + const text = `这句一言来自 「${result.from}」,是 ${result.creator} 在 hitokoto.cn 投稿的。`; + showMessage(result.hitokoto, 6000, 9); + setTimeout(() => { + showMessage(text, 4000, 9); + }, 6000); + }); + } + + function showMessage(text, timeout, priority) { + if (!text || (sessionStorage.getItem("waifu-text") && sessionStorage.getItem("waifu-text") > priority)) return; + if (messageTimer) { + clearTimeout(messageTimer); + messageTimer = null; + } + text = randomSelection(text); + sessionStorage.setItem("waifu-text", priority); + const tips = document.getElementById("waifu-tips"); + tips.innerHTML = text; + tips.classList.add("waifu-tips-active"); + messageTimer = setTimeout(() => { + sessionStorage.removeItem("waifu-text"); + tips.classList.remove("waifu-tips-active"); + }, timeout); + } + + (function initModel() { + let modelId = localStorage.getItem("modelId"), + modelTexturesId = localStorage.getItem("modelTexturesId"); + if (modelId === null) { + // 首次访问加载 指定模型 的 指定材质 + modelId = 1; // 模型 ID + modelTexturesId = 53; // 材质 ID + } + loadModel(modelId, modelTexturesId); + fetch(config.waifuPath) + .then(response => response.json()) + .then(result => { + window.addEventListener("mouseover", event => { + for (let { selector, text } of result.mouseover) { + if (!event.target.matches(selector)) continue; + text = randomSelection(text); + text = text.replace("{text}", event.target.innerText); + showMessage(text, 4000, 8); + return; + } + }); + window.addEventListener("click", event => { + for (let { selector, text } of result.click) { + if (!event.target.matches(selector)) continue; + text = randomSelection(text); + text = text.replace("{text}", event.target.innerText); + showMessage(text, 4000, 8); + return; + } + }); + result.seasons.forEach(({ date, text }) => { + const now = new Date(), + after = date.split("-")[0], + before = date.split("-")[1] || after; + if ((after.split("/")[0] <= now.getMonth() + 1 && now.getMonth() + 1 <= before.split("/")[0]) && (after.split("/")[1] <= now.getDate() && now.getDate() <= before.split("/")[1])) { + text = randomSelection(text); + text = text.replace("{year}", now.getFullYear()); + //showMessage(text, 7000, true); + messageArray.push(text); + } + }); + }); + })(); + + async function loadModelList() { + const response = await fetch(`${cdnPath}model_list.json`); + modelList = await response.json(); + } + + async function loadModel(modelId, modelTexturesId, message) { + localStorage.setItem("modelId", modelId); + localStorage.setItem("modelTexturesId", modelTexturesId); + showMessage(message, 4000, 10); + if (useCDN) { + if (!modelList) await loadModelList(); + const target = randomSelection(modelList.models[modelId]); + loadlive2d("live2d", `${cdnPath}model/${target}/index.json`); + } else { + loadlive2d("live2d", `${apiPath}get/?id=${modelId}-${modelTexturesId}`); + console.log(`Live2D 模型 ${modelId}-${modelTexturesId} 加载完成`); + } + } + + async function loadRandModel() { + const modelId = localStorage.getItem("modelId"), + modelTexturesId = localStorage.getItem("modelTexturesId"); + if (useCDN) { + if (!modelList) await loadModelList(); + const target = randomSelection(modelList.models[modelId]); + loadlive2d("live2d", `${cdnPath}model/${target}/index.json`); + showMessage("我的新衣服好看嘛?", 4000, 10); + } else { + // 可选 "rand"(随机), "switch"(顺序) + fetch(`${apiPath}rand_textures/?id=${modelId}-${modelTexturesId}`) + .then(response => response.json()) + .then(result => { + if (result.textures.id === 1 && (modelTexturesId === 1 || modelTexturesId === 0)) showMessage("我还没有其他衣服呢!", 4000, 10); + else loadModel(modelId, result.textures.id, "我的新衣服好看嘛?"); + }); + } + } + + async function loadOtherModel() { + let modelId = localStorage.getItem("modelId"); + if (useCDN) { + if (!modelList) await loadModelList(); + const index = (++modelId >= modelList.models.length) ? 0 : modelId; + loadModel(index, 0, modelList.messages[index]); + } else { + fetch(`${apiPath}switch/?id=${modelId}`) + .then(response => response.json()) + .then(result => { + loadModel(result.model.id, 0, result.model.message); + }); + } + } +} + +function initWidget(config, apiPath) { + if (typeof config === "string") { + config = { + waifuPath: config, + apiPath + }; + } + document.body.insertAdjacentHTML("beforeend", `
+ 看板娘 +
`); + const toggle = document.getElementById("waifu-toggle"); + toggle.addEventListener("click", () => { + toggle.classList.remove("waifu-toggle-active"); + if (toggle.getAttribute("first-time")) { + loadWidget(config); + toggle.removeAttribute("first-time"); + } else { + localStorage.removeItem("waifu-display"); + document.getElementById("waifu").style.display = ""; + setTimeout(() => { + document.getElementById("waifu").style.bottom = 0; + }, 0); + } + }); + if (localStorage.getItem("waifu-display") && Date.now() - localStorage.getItem("waifu-display") <= 86400000) { + toggle.setAttribute("first-time", true); + setTimeout(() => { + toggle.classList.add("waifu-toggle-active"); + }, 0); + } else { + loadWidget(config); + } +} + +window.initWidget = initWidget; diff --git a/waifu.css b/waifu.css index 5833d79..d825504 100755 --- a/waifu.css +++ b/waifu.css @@ -62,7 +62,7 @@ transition: opacity .2s; } -#waifu-tips .fa-lg { +#waifu-tips svg { color: #0099cc; } @@ -90,17 +90,21 @@ opacity: 1; } -#waifu-tool .fa-lg { - color: #7b8c9d; - cursor: pointer; +#waifu-tool span { display: block; height: 30px; text-align: center; - transition: color .3s; } -#waifu-tool .fa-lg:hover { - color: #0684bd; /* #34495e */ +#waifu-tool svg { + fill: #7b8c9d; + cursor: pointer; + height: 25px; + transition: fill .3s; +} + +#waifu-tool svg:hover { + fill: #0684bd; /* #34495e */ } @keyframes shake {