尼采般地抒情

公告栏

此网站主题为本人手写主题, 主题待开源···

站点信息

文章总数目: 305
已运行时间: 1063
目录
  1. 安装hexo-helper-live2d
  2. 配置自定义蕾姆模型
    1. 文件配置
    2. _config.yml配置
    3. 最终 live2d 效果
  3. 配置三方插件模型
  4. 附:给 live2d 添加看板功能
    1. 添加看板样式
    2. 添加看板逻辑代码
    3. 最终看板效果
  5. 参考

尼采般地抒情

尼采般地抒情

公告栏

此网站主题为本人手写主题, 主题待开源···

站点信息

文章总数目: 305
已运行时间: 1063


前言:为自己站点配置雷姆live2d,并在live2d模型的基础上附加添加看板功能。

安装hexo-helper-live2d

npm install --save hexo-helper-live2d

配置自定义蕾姆模型

文件配置

_config.yml配置

## live2d

live2d:
enable: true # 是否开启 live2d
scriptFrom: local # 脚本从本地引入
pluginRootPath: live2d/ # 插件在站点上的根目录(相对路径)
pluginJsPath: lib/ # 脚本文件相对与插件根目录路径
pluginModelPath: assets/ # 模型文件相对与插件根目录路径
tagMode: false # 标签模式, 是否仅替换 live2d tag 标签而非插入到所有页面中
debug: false # 调试, 是否在控制台输出日志
model:
use: remu # 填写放进 live2d_models 文件夹中的模型文件夹名字
scale: 1 # canvas 模型与 canvas 的缩放
display:
width: 150 # 宽度
height: 300 # 高度
position: left # 显示位置
hOffset: 0 #水平偏移
vOffset: -12 #垂直偏移
mobile:
show: true # 手机端是否显示
scale: 1 # 移动设备上的缩放
react:
opacity: 1 # 透明度

PS:按需加载 live2d 标签

上述配置中tagMode: true则代表需要在主题中单独给出需要显示的页面

最终 live2d 效果

配置三方插件模型

上述步骤是下载 remu 源文件然后自定义的 remu 模型,也可以下载社区封装好 npm 插件的模型,本文做自定义模型的说明,故不做该部分详述。相关参考如下:

附:给 live2d 添加看板功能

上述所有步骤完成 live2d 的安装配置,在此基础上还可以给 live2d 添加看板功能,就如本文第一张图片那种效果

添加看板样式

添加静态样式即可:

\_live2d/remu.styl
// 收缩之后的左下角的贴附样式
#waifu-toggle {
background-color: #3f51b5;
border-radius: 5px;
bottom: 66px;
color: #fff;
cursor: pointer;
font-size: 14px;
left: 5px;
margin-left: -90px;
padding: 5px 2px 5px 5px;
position: fixed;
transition: margin-left 1s;
width: 60px;
writing-mode: vertical-rl;
}

#waifu-toggle.waifu-toggle-active {
margin-left: -40px;
}

#waifu-toggle.waifu-toggle-active:hover {
margin-left: -30px;
}

// 容纳雷姆的盒子
#waifu {
bottom: -1000px;
left: 0;
line-height: 0;
margin-bottom: -10px;
position: fixed;
transform: translateY(3px);
transition: transform .3s ease-in-out, bottom 3s ease-in-out;
z-index: 1;
}

#waifu:hover {
transform: translateY(0);
}

// 文字提示框
#waifu-tips {
animation: shake 50s ease-in-out 5s infinite;
background-color: rgba(100,149,237, .5);
border: 1px solid rgba(30,144,255, .62);
border-radius: 12px;
box-shadow: 0 3px 15px 2px rgba(191, 158, 118, .2);
font-size: 14px;
line-height: 24px;
margin: -30px 20px;
min-height: 70px;
opacity: 0;
overflow: hidden;
padding: 5px 10px;
position: absolute;
text-overflow: ellipsis;
transition: opacity 1s;
width: 180px;
word-break: break-all;
}

#waifu-tips.waifu-tips-active {
opacity: 1;
transition: opacity .2s;
}

#waifu-tips span
color: rgba(0,0,255,.7); // 文字提示框中的重点字

// 容纳雷姆的外加小盒子
#waifu #live2d {
cursor: grab;
height: 300px;
position: relative;
width: 200px;
}

#waifu #live2d:active {
cursor: grabbing;
}

// 小按钮组件
#waifu-tool {
color: #0684bd;
opacity: 0.5;
position: absolute;
left: 5px
top: 70px;
transition: opacity 1s;
}

#waifu:hover #waifu-tool {
opacity: 1;
}

// 小按钮的 span 样式
#waifu-tool span {
color: #0684bd;
cursor: pointer;
display: block;
line-height: 30px;
text-align: center;
transition: color .3s;
}

#waifu-tool span:hover {
color: #0684bd; /_ #34495e _/
}

@keyframes shake {
2% {
transform: translate(.5px, -1.5px) rotate(-.5deg);
}

4% {
transform: translate(.5px, 1.5px) rotate(1.5deg);
}

6% {
transform: translate(1.5px, 1.5px) rotate(1.5deg);
}

8% {
transform: translate(2.5px, 1.5px) rotate(.5deg);
}

10% {
transform: translate(.5px, 2.5px) rotate(.5deg);
}

12% {
transform: translate(1.5px, 1.5px) rotate(.5deg);
}

14% {
transform: translate(.5px, .5px) rotate(.5deg);
}

16% {
transform: translate(-1.5px, -.5px) rotate(1.5deg);
}

18% {
transform: translate(.5px, .5px) rotate(1.5deg);
}

20% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}

22% {
transform: translate(.5px, -1.5px) rotate(1.5deg);
}

24% {
transform: translate(-1.5px, 1.5px) rotate(-.5deg);
}

26% {
transform: translate(1.5px, .5px) rotate(1.5deg);
}

28% {
transform: translate(-.5px, -.5px) rotate(-.5deg);
}

30% {
transform: translate(1.5px, -.5px) rotate(-.5deg);
}

32% {
transform: translate(2.5px, -1.5px) rotate(1.5deg);
}

34% {
transform: translate(2.5px, 2.5px) rotate(-.5deg);
}

36% {
transform: translate(.5px, -1.5px) rotate(.5deg);
}

38% {
transform: translate(2.5px, -.5px) rotate(-.5deg);
}

40% {
transform: translate(-.5px, 2.5px) rotate(.5deg);
}

42% {
transform: translate(-1.5px, 2.5px) rotate(.5deg);
}

44% {
transform: translate(-1.5px, 1.5px) rotate(.5deg);
}

46% {
transform: translate(1.5px, -.5px) rotate(-.5deg);
}

48% {
transform: translate(2.5px, -.5px) rotate(.5deg);
}

50% {
transform: translate(-1.5px, 1.5px) rotate(.5deg);
}

52% {
transform: translate(-.5px, 1.5px) rotate(.5deg);
}

54% {
transform: translate(-1.5px, 1.5px) rotate(.5deg);
}

56% {
transform: translate(.5px, 2.5px) rotate(1.5deg);
}

58% {
transform: translate(2.5px, 2.5px) rotate(.5deg);
}

60% {
transform: translate(2.5px, -1.5px) rotate(1.5deg);
}

62% {
transform: translate(-1.5px, .5px) rotate(1.5deg);
}

64% {
transform: translate(-1.5px, 1.5px) rotate(1.5deg);
}

66% {
transform: translate(.5px, 2.5px) rotate(1.5deg);
}

68% {
transform: translate(2.5px, -1.5px) rotate(1.5deg);
}

70% {
transform: translate(2.5px, 2.5px) rotate(.5deg);
}

72% {
transform: translate(-.5px, -1.5px) rotate(1.5deg);
}

74% {
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
}

76% {
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
}

78% {
transform: translate(-1.5px, 2.5px) rotate(.5deg);
}

80% {
transform: translate(-1.5px, .5px) rotate(-.5deg);
}

82% {
transform: translate(-1.5px, .5px) rotate(-.5deg);
}

84% {
transform: translate(-.5px, .5px) rotate(1.5deg);
}

86% {
transform: translate(2.5px, 1.5px) rotate(.5deg);
}

88% {
transform: translate(-1.5px, .5px) rotate(1.5deg);
}

90% {
transform: translate(-1.5px, -.5px) rotate(-.5deg);
}

92% {
transform: translate(-1.5px, -1.5px) rotate(1.5deg);
}

94% {
transform: translate(.5px, .5px) rotate(-.5deg);
}

96% {
transform: translate(2.5px, -.5px) rotate(-.5deg);
}

98% {
transform: translate(-1.5px, -1.5px) rotate(-.5deg);
}

0%, 100% {
transform: translate(0, 0) rotate(0);
}
}

添加看板逻辑代码

./part/live2d.js
function loadWidget(config) {
let { waifuPath, 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", `<div id="waifu">
                <div id="waifu-tips"></div>
                <canvas id="live2d"></canvas>
                <div id="waifu-tool">
                    <span class="fa fa-comments"></span>
                    <span class="fa fa-link"></span>
                    <span class="fa fa-compress"></span>
                </div>
            </div>`);
// 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() {
// TODO: 第一个 icon,说话
document.querySelector("#waifu-tool .fa-comments").addEventListener("click", showHitokoto);
// TODO: 第二个 icon,貌似是什么游戏
// document.querySelector("#waifu-tool .fa-paper-plane").addEventListener("click", () => {
// if (window.Asteroids) {
// if (!window.ASTEROIDSPLAYERS) window.ASTEROIDSPLAYERS = [];
// window.ASTEROIDSPLAYERS.push(new Asteroids());
// } else {
// const script = document.createElement("script");
// script.src = "https://cdn.jsdelivr.net/gh/stevenjoezhang/asteroids/asteroids.js";
// document.head.appendChild(script);
// }
// });
// TODO: 第三个 icon,换人物
// document.querySelector("#waifu-tool .fa-user-circle").addEventListener("click", loadOtherModel);
// TODO: 第四个 icon,应该是换装
// document.querySelector("#waifu-tool .fa-street-view").addEventListener("click", loadRandModel);
// TODO: 第五个 icon,拍照
// document.querySelector("#waifu-tool .fa-camera-retro").addEventListener("click", () => {
// showMessage("照好了嘛,是不是很可爱呢?", 6000, 9);
// Live2D.captureName = "photo.png";
// Live2D.captureFrame = true;
// });
// TODO: 第六个 icon,原项目地址
document.querySelector("#waifu-tool .fa-link").addEventListener("click", () => {
open("https://www.wztlink1013.com/about/");
});
// TODO: 第七个 icon,隐藏看板娘

    document.querySelector("#waifu-tool .fa-compress").addEventListener("click", () => {
      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);


    });


    window.addEventListener("copy", () => {
      showMessage("你都复制了些什么呀,转载要记得加上出处哦!", 6000, 9);
    });
    window.addEventListener("visibilitychange", () => {
      if (!document.hidden) showMessage("哇,你终于回来了~", 6000, 9);
    });

})();

// 首页展示特定文字
(function welcomeMessage() {
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 = "你是夜猫子呀?这么晚还不睡觉,明天起的来嘛?";
} else if (document.referrer !== "") {
const referrer = new URL(document.referrer),
domain = referrer.hostname.split(".")[1];
if (location.hostname === referrer.hostname) text = `欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;
else if (domain === "baidu") text = `Hello!来自 百度搜索 的朋友<br>你是搜索 <span>${referrer.search.split("&wd=")[1].split("&")[0]}</span> 找到的我吗?`;
else if (domain === "so") text = `Hello!来自 360搜索 的朋友<br>你是搜索 <span>${referrer.search.split("&q=")[1].split("&")[0]}</span> 找到的我吗?`;
else if (domain === "google") text = `Hello!来自 谷歌搜索 的朋友<br>欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;
else text = `Hello!来自 <span>${referrer.hostname}</span> 的朋友`;
} else {
text = `欢迎阅读<span>「${document.title.split(" - ")[0]}」</span>`;
}
showMessage(text, 7000, 8);
})();
// TODO: 服务于第一个 icon
function showHitokoto() {
// 增加 hitokoto.cn 的 API
fetch("https://v1.hitokoto.cn")
.then(response => response.json())
.then(result => {
const text = `这句一言来自 <span>「${result.from}」</span>,是 <span>${result.creator}</span> 在 hitokoto.cn 投稿的。`;
showMessage(result.hitokoto, 6000, 9);
setTimeout(() => {
showMessage(text, 4000, 9);
}, 6000);
});
}
// 没有 bug 展示特定状态下的文字
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 initWidget(config, apiPath) {
if (typeof config === "string") config = { waifuPath: config, apiPath };
document.body.insertAdjacentHTML("beforeend", `<div id="waifu-toggle">
                <span>雷姆</span>
            </div>`);
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);
}
// TODO: 一开始判断是否隐藏雷姆代码

});
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);
}

}

export {
initWidget, //导出对象
}

在base.js中初始化看板逻辑代码

import {initWidget} from './part/live2d.js';

// live2d 初始化
initWidget({
waifuPath: "/data/remu.json",
cdnPath: "https://github.com/fghrsh/live2d_api/tree/1.0.1/"
});

最终看板效果

参考

  • TODO: 优化资源加载方式,模型的静态资源使用外链的形式较为合理,以及插件每次在开发模式下都会编译文件,都会带来开发环境以及生产环境上的不便

评论区

Twikoo giscus