# ShokaX 0.3.6 迁移 0.5.4

由于差异巨大,只得采取重新安装的方式

# 安装ShokaX

pnpm add hexo-cli -g
hexo init
pnpm add hexo-theme-shokax
node ./node_modules/hexo-theme-shokax/toolbox/hoistdep.mjs

同时为了方便管理,创建软链接

mklink /J .\themes\shokax .\node_modules\hexo-theme-shokax

# 启用主题

删除根目录下的_config.landscape.yml

打开根目录下的_config.yml

  1. theme更改为shokax

  2. 停用默认代码高亮syntax_highlighter: false

  3. 添加如下配置在最末尾

    markdown:
      render: # 渲染器设置
        html: false # 过滤 HTML 标签
        xhtmlOut: true # 使用 '/' 来闭合单标签 (比如 <br />)。
        breaks: true # 转换段落里的 '\n' 到 <br>。
        linkify: true # 将类似 URL 的文本自动转换为链接。
        typographer:
        quotes: "“”‘’"
      plugins: # markdown-it 插件设置
        - plugin:
            name: markdown-it-toc-and-anchor
            enable: true
            options: # 文章目录以及锚点应用的 class 名称,shoka 系主题必须设置成这样
              tocClassName: "toc"
              anchorClassName: "anchor"
        - plugin:
            name: markdown-it-multimd-table
            enable: true
            options:
              multiline: true
              rowspan: true
              headerless: true
        - plugin:
            name: ./markdown-it-furigana
            enable: true
            options:
              fallbackParens: "()"
        - plugin:
            name: ./markdown-it-spoiler
            enable: true
            options:
              title: "你知道得太多了"
  4. 启动文件压缩,添加在最末尾

    minify:
      js:
        enable: false # ShokaX 自带 esbuild 优化,不建议开启,其他主题建议开启
        exclude: # 排除文件,接受 string[],需符合 micromatch 格式
      css:
        enable: true # 开启 CSS 优化
        options:
          targets: ">= 0.5%" # browserslist 格式的 target
        exclude: # 排除文件,接受 string[],需符合 micromatch 格式
      html:
        minifier: html-minifier
        enable: true # 开启 HTML 优化
        options:
          comments: false # 是否保留注释内容
        exclude: # 排除文件,接受 string[],需符合 micromatch 格式
      image:
        enable: false # 开启图片预处理和自动 WebP 化
        options:
          avif: false
          webp: true # 预留配置项,现版本无作用
          quality: 80 # 质量,支持1-100的整数、lossless或nearLossless
          effort: 2 # CPU 工作量,0-6之间的整数(越低越快)
          replaceSrc: false # 自动替换生成html中的本地图片链接为webp链接
          # 对于使用 CI 工作流部署的用户,请注意本配置可能导致图片 404,如果出现请关闭
          # 我们更建议使用 Service Worker 来在用户侧实现 replaceSrc 的功能,这将能够以一种侵入式更小的方式实现链接替换
        exclude:
  5. 开启feed生成,添加在最末尾

    feed:
      limit: 20
      order_by: "-date"
      tag_dir: false
      category_dir: false
      rss:
        enable: true
        template: "node_modules/hexo-theme-shokax/layout/_alternate/rss.ejs"
        output: "rss.xml"
      atom:
        enable: true
        template: "node_modules/hexo-theme-shokax/layout/_alternate/atom.ejs"
        output: "atom.xml"
      jsonFeed:
        enable: true
        template: "node_modules/hexo-theme-shokax/layout/_alternate/json.ejs"
        output: "feed.json"

同时,将<root>/themes/shokax/source/assets移动到<root>/source/_data/assets/,同时添加自己头像avatar.jpg

<root>/themes/shokax/_images.yml中添加至少6张图片的链接

随后主题即可正常使用

# 加载动画替换

更改<root>/themes/shokax/layout/_partials/loading.pug

div(id="loading")
    div(class="loader")
        div(class="dot")
        div(class="dot")
        div(class="dot")
        div(class="dot")
        div(class="dot")

更改<root>/themes/shokax/layout/_partials/loading.pug

#loading {
  @extend $fix-fullscreen;
  background-color: var(--grey-1);
  if(!hexo-config('loader.start')) {
    display: none;
  }
}

body
  margin 0

.loader
  position absolute
  top 50%
  left 40%
  margin-left 10%
  transform translate3d(-50%,-50%,0)

.dot
  width 24px
  height 24px
  background #3ac
  border-radius 100%
  display inline-block
  animation slide 1s infinite

for n in (1..5)
  .dot:nth-child({n})
    animation-delay (.1s * n)
    background red(#3ac, (50 * n))

@keyframes slide
  0%
    transform scale(1)
  50%
    opacity .3
    transform scale(2)
  100%
    transform scale(1)

# Live2D部署

# 本地编译

首先去官方仓库克隆项目stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platform

随后去Live2D官网下载SDK for Web 下载Live2D Cubism SDK for Web | Live2D Cubism

解压SDK后放入项目src文件夹中,路径参考src/CubismSdkForWeb-5-r.4

修改tsconfig.json,排除官方示例文件夹

{
  "compilerOptions": {
    "target": "es2017",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": false,
    "skipLibCheck": true,
    "removeComments": true,
    "allowJs": true,
    "paths": {
      "@framework/*": [
        "./src/CubismSdkForWeb-5-r.4/Framework/src/*"
      ],
      "@demo/*": [
        "./src/CubismSdkForWeb-5-r.4/Samples/TypeScript/Demo/src/*"
      ]
    }
  },
  "include": ["src"],
  "exclude": [
    "node_modules", 
    "build", 
    "dist",
    "src/CubismSdkForWeb-5-r.4/Samples/**/*.config.*"
  ]
}

随后在终端输入npm install,安装完毕后输入npm run build开始构建

# 推送仓库

构建完毕后在项目根目录下创建models文件夹,并放入想使用的模型,我以莲的模型为例,路径为/models/ren

随后将项目推送到github,然后访问发布一个版本,这里我Release填为0.1.0

访问地址https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径,如果不填版本号默认拉取最新版

关于autoload.js,即为https://fastly.jsdelivr.net/gh/AquaOH/live2d-widget/dist/autoload.js

关于模型链接,即为https://fastly.jsdelivr.net/gh/AquaOH/live2d-widget/models/ren/L.model3.json

需要一段时间才能够同步,这个过程通常极其缓慢...

# 配置文件

为了能够正常使用,还需要修改autoload.js

const live2d_path = 'https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0-rc.6/dist/';的值改为你的值,比如我的为https://fastly.jsdelivr.net/gh/AquaOH/live2d-widget/dist/

同时在waifu-tips.json中添加需要的模型,示例如下

  "models": [
    {
      "name": "Ren",
      "paths": [
        "https://fastly.jsdelivr.net/gh/AquaOH/live2d-widget/models/ren/L.model3.json"
      ],
      "message": "枯れない世界と終わる花"
    },
    {
      "name": "Potion-Maker/Pio",
      "paths": [
        "https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/model/Potion-Maker/Pio/index.json"
      ],
      "message": "来自 Potion Maker 的 Pio 酱 ~"
    },
    {
      "name": "Potion-Maker/Tia",
      "paths": [
        "https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/model/Potion-Maker/Tia/index.json"
      ],
      "message": "来自 Potion Maker 的 Tia 酱 ~"
    }
  ]

# 引入博客

/themes/shokax/layout/_partials/layout.pug中找到这一段:

        != _js('siteInit.js')

        != shokax_inject('bodyEnd')

在这两行之间添加:

        != _js('siteInit.js')
        
        script(src="https://fastly.jsdelivr.net/gh/AquaOH/live2d-widget/dist/autoload.js")
        
        != shokax_inject('bodyEnd')

同时设置下样式,在文件<Hexo根目录>\source\_data\custom.styl中添加如下样式调节按钮颜色和live2d层级(没有请创建)

/* 修复 Live2D 的完整层级结构 */
#waifu,
#waifu-canvas,
#live2d {
  z-index: 99999 !important;
}


#waifu-toggle {
  background-color: #ffc1cc !important; 
  
  &:hover {
    background-color: #ff8a95 !important; 
  }
}

#waifu-tool svg {
  fill: #ffc1cc !important;
  
  &:hover {
    fill: #ff8a95 !important;
  }
}

[data-theme="dark"] {
  #waifu-toggle {
    background-color: #cc9199 !important; 
    
    &:hover {
      background-color: #cc5a66 !important; 
    }
  }
  
  #waifu-tool svg {
    fill: #cc9199 !important;
    
    &:hover {
      fill: #cc5a66 !important;
    }
  }
}

# 工具按钮居右

在文件<Hexo根目录>\source\_data\custom.styl中添加如下样式调节按钮位置

#tool {
  left: auto !important;
  right: 1rem !important;
  
  &.affix {
    right: 0px !important;
  }
}

# 背景图片小方格

在文件<Hexo根目录>\source\_data\custom.styl中添加如下样式

#header::before {
  background: url("https://cdn.jsdelivr.net/gh/lavender816/CDN@1.8/img/dot.jpg");
  content:'';
  position:absolute;
  height:100vh;
  top:0;
  bottom:0;
  left:0;
  right:0;
  z-index:-4;
  background-attachment:fixed;
}

# 更改加载动画

这两个文件替换成你想要的加载动画就行

themes/shokax/layout/_partials/loading.pug

    div(class="loader")
        div(class="dot")
        div(class="dot")
        div(class="dot")
        div(class="dot")
        div(class="dot")

themes/shokax/source/css/_common/components/third-party/loading.styl

body
  margin 0

.loader
  position absolute
  top 50%
  left 40%
  margin-left 10%
  transform translate3d(-50%,-50%,0)

.dot
  width 24px
  height 24px
  background #3ac
  border-radius 100%
  display inline-block
  animation slide 1s infinite

for n in (1..5)
  .dot:nth-child({n})
    animation-delay (.1s * n)
    background red(#3ac, (50 * n))

@keyframes slide
  0%
    transform scale(1)
  50%
    opacity .3
    transform scale(2)
  100%
    transform scale(1)

# 漂浮物

放入图片source/_data/assets/float.png

添加新文件themes/shokax/scripts/sakura.js

themes/shokax/scripts/sakura.js

添加新文件views/sakura.pug

script.
  // 漂浮物
  // 新增函数 
  // 首先获取 Url,然后把 Url 通过 // 截成两部分,再从后一部分中截取相对路径。如果截取到的相对路径中有参数,则把参数去掉。
  // 获取相对路径
  function GetUrlRelativePath()
  {
      var url = document.location.toString();
      var arrUrl = url.split("//");

      var start = arrUrl[1].indexOf("/");
      var relUrl = arrUrl[1].substring(start);//stop省略,截取从start开始到结尾的所有字符

      if(relUrl.indexOf("?") != -1){
          relUrl = relUrl.split("?")[0];
      }
      return relUrl;
  }
  var allowAll = true; // true 则允许所有网页存在漂浮物
  // 允许显示漂浮物的网址列表
  var urlAllowList = [
      "/about/",
      "friends/",
  ]

  var isAllowFloat = false;   // 全局变量,允许使用漂浮特效

  // 判断
  function decide(){
      isAllowFloat = true;
      if(!allowAll){
          // 判断当前页面是否为指定页面
          var url = GetUrlRelativePath();
          var i = 0;
          for(;i<urlAllowList.length;i++){
              if(url===urlAllowList[i]){
                  // console.log(i);
                  isAllowFloat = true;
                  break;
              }
          }
          if(i===urlAllowList.length){
              isAllowFloat = false;
          }
      }
      console.log(isAllowFloat)
      if(isAllowFloat)startFloat();
  }

  var stop, staticx;
  var img = new Image();
  img.src = "/assets/float.png"; // 图片

  function Float(x, y, s, r, fn) {
      this.x = x;
      this.y = y;
      this.s = s;
      this.r = r;
      this.fn = fn;
  }

  Float.prototype.draw = function(cxt) {
      cxt.save();
      var xc = 40 * this.s / 4;
      cxt.translate(this.x, this.y);
      cxt.rotate(this.r);
      cxt.drawImage(img, 0, 0, 35 * this.s, 35 * this.s)
      // 漂浮物大小
      cxt.restore();
  }

  Float.prototype.update = function() {
      this.x = this.fn.x(this.x, this.y);
      this.y = this.fn.y(this.y, this.y);
      this.r = this.fn.r(this.r);
      if (this.x > window.innerWidth || this.x < 0 || this.y > window.innerHeight || this.y < 0) {
          this.r = getRandom('fnr');
          if (Math.random() > 0.4) {
              this.x = getRandom('x');
              this.y = 0;
              this.s = getRandom('s');
              this.r = getRandom('r');
          } else {
              this.x = window.innerWidth;
              this.y = getRandom('y');
              this.s = getRandom('s');
              this.r = getRandom('r');
          }
      }
  }

  FloatList = function() {
      this.list = [];
  }
  FloatList.prototype.push = function(float) {
      this.list.push(float);
  }
  FloatList.prototype.update = function() {
      for (var i = 0, len = this.list.length; i < len; i++) {
          this.list[i].update();
      }
  }
  FloatList.prototype.draw = function(cxt) {
      for (var i = 0, len = this.list.length; i < len; i++) {
          this.list[i].draw(cxt);
      }
  }
  FloatList.prototype.get = function(i) {
      return this.list[i];
  }
  FloatList.prototype.size = function() {
      return this.list.length;
  }

  function getRandom(option) {
      var ret, random;
      switch (option) {
      case 'x':
          ret = Math.random() * window.innerWidth;
          break;
      case 'y':
          ret = Math.random() * window.innerHeight;
          break;
      case 's':
          ret = Math.random();
          break;
      case 'r':
          ret = Math.random() * 6;
          break;
      case 'fnx':
          random = -0.5 + Math.random() * 1;
          ret = function(x, y) {
              return x + 0.5 * random - 0.6;
              //x 轴速度
          }
          ;
          break;
      case 'fny':
          random = 0.8 + Math.random() * 0.7
          //y 轴速度
          ret = function(x, y) {
              return y + random;
          }
          ;
          break;
      case 'fnr':
          random = Math.random() * 0.03;
          ret = function(r) {
              return r + random;
          }
          ;
          break;
      }
      return ret;
  }

  function startFloat() {

      requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame;
      var canvas = document.createElement('canvas'), cxt;
      staticx = true;
      canvas.height = window.innerHeight;
      canvas.width = window.innerWidth;
      canvas.setAttribute('style', 'position: fixed;left: 0;top: 0;pointer-events: none;');
      canvas.setAttribute('id', 'canvas_float');
      document.getElementsByTagName('body')[0].appendChild(canvas);
      cxt = canvas.getContext('2d');
      var floatList = new FloatList();
      for (var i = 0; i < 10; i++) {
          // 漂浮物数量
          var float, randomX, randomY, randomS, randomR, randomFnx, randomFny;
          randomX = getRandom('x');
          randomY = getRandom('y');
          randomR = getRandom('r');
          randomS = getRandom('s');
          randomFnx = getRandom('fnx');
          randomFny = getRandom('fny');
          randomFnR = getRandom('fnr');
          float = new Float(randomX,randomY,randomS,randomR,{
              x: randomFnx,
              y: randomFny,
              r: randomFnR
          });
          float.draw(cxt);
          floatList.push(float);
      }
      stop = requestAnimationFrame(function() {
          cxt.clearRect(0, 0, canvas.width, canvas.height);
          floatList.update();
          floatList.draw(cxt);
          stop = requestAnimationFrame(arguments.callee);
      })
  }

  window.onresize = function() {
      if(!isAllowFloat)return;
      var canvasSnow = document.getElementById('canvas_float');
      canvasSnow.width = window.innerWidth;
      canvasSnow.height = window.innerHeight;
  }

  function stopp(e) {
      if (!e && document.getElementById("canvas_float")) {
          var child = document.getElementById("canvas_float");
          child.parentNode.removeChild(child);
          window.cancelAnimationFrame(stop);
      } else if (e && !document.getElementById("canvas_float")) {
          decide();
      }
  }

  window.addEventListener("DOMContentLoaded",decide);