尼采般地抒情

尼采般地抒情

尼采般地抒情

音乐盒

站点信息

文章总数目: 315
已运行时间: 1554


前言:为自己站点配置雷姆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(&quot;#waifu-tool .fa-compress&quot;).addEventListener(&quot;click&quot;, () =&gt; {
  localStorage.setItem(&quot;waifu-display&quot;, Date.now());
  showMessage(&quot;愿你有一天能与重要的人重逢。&quot;, 2000, 11);
  document.getElementById(&quot;waifu&quot;).style.bottom = &quot;-500px&quot;; // 隐藏大盒子
  setTimeout(() =&gt; {
    document.getElementById(&quot;waifu&quot;).style.display = &quot;none&quot;;
    document.getElementById(&quot;waifu-toggle&quot;).classList.add(&quot;waifu-toggle-active&quot;);
  }, 3000);


});


window.addEventListener(&quot;copy&quot;, () =&gt; {
  showMessage(&quot;你都复制了些什么呀,转载要记得加上出处哦!&quot;, 6000, 9);
});
window.addEventListener(&quot;visibilitychange&quot;, () =&gt; {
  if (!document.hidden) showMessage(&quot;哇,你终于回来了~&quot;, 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 = 欢迎阅读&lt;span&gt;「${document.title.split(&quot; - &quot;)[0]}」&lt;/span&gt;;
else if (domain === "baidu") text = Hello!来自 百度搜索 的朋友&lt;br&gt;你是搜索 &lt;span&gt;${referrer.search.split(&quot;&amp;wd=&quot;)[1].split(&quot;&amp;&quot;)[0]}&lt;/span&gt; 找到的我吗?;
else if (domain === "so") text = Hello!来自 360搜索 的朋友&lt;br&gt;你是搜索 &lt;span&gt;${referrer.search.split(&quot;&amp;q=&quot;)[1].split(&quot;&amp;&quot;)[0]}&lt;/span&gt; 找到的我吗?;
else if (domain === "google") text = Hello!来自 谷歌搜索 的朋友&lt;br&gt;欢迎阅读&lt;span&gt;「${document.title.split(&quot; - &quot;)[0]}」&lt;/span&gt;;
else text = Hello!来自 &lt;span&gt;${referrer.hostname}&lt;/span&gt; 的朋友;
} else {
text = 欢迎阅读&lt;span&gt;「${document.title.split(&quot; - &quot;)[0]}」&lt;/span&gt;;
}
showMessage(text, 7000, 8);
})();
// TODO: 服务于第一个icon
function showHitokoto() {
// 增加 hitokoto.cn 的 API
fetch("https://v1.hitokoto.cn")
.then(response => response.json())
.then(result => {
const text = 这句一言来自 &lt;span&gt;「${result.from}」&lt;/span&gt;,是 &lt;span&gt;${result.creator}&lt;/span&gt; 在 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", &lt;div id=&quot;waifu-toggle&quot;&gt; &lt;span&gt;雷姆&lt;/span&gt; &lt;/div&gt;);
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: 优化资源加载方式,模型的静态资源使用外链的形式较为合理,以及插件每次在开发模式下都会编译文件,都会带来开发环境以及生产环境上的不便

评论区