尼采般地抒情

尼采般地抒情

尼采般地抒情

音乐盒

站点信息

文章总数目: 321
已运行时间: 1782


前言:为自己站点配置雷姆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: 优化资源加载方式,模型的静态资源使用外链的形式较为合理,以及插件每次在开发模式下都会编译文件,都会带来开发环境以及生产环境上的不便

评论区

什么都不舍弃,什么也改变不了