Hexo博客美化

本文最后更新于 2024年6月4日 上午

理论上讲,这里实现的效果是基于Hexo的,对于实际使用的主题没有要求,都是适用的。我使用的是Hexo Fluid主题。在这里也感谢各位开发人员的付出,让我们可以更便捷、美观地搭建出自己的个人博客。

写在前面的话

存在的问题

因为我是通过npm命令直接安装的Hexo Fluid主题:

1
npm install --save hexo-theme-fluid

万一后期需要更新主题:

1
npm update --save hexo-theme-fluid

直接使用命令行更新会覆盖掉自己对主题文件夹内做出的所有更改(血泪教训),包括图片,自定义的.js和.css文件等。本来以为只会覆盖掉不同的,不会影响新增的,失策了。这是需要注意、寻找解决办法(解决代码冲突,保留自己修改的部分)和及时备份的。

引用Fluid魔改笔记-Asteri5m中的前言:Hexo Fluid主题是一款完整度比较高的主题了,一般不要修改源代码,不然导致后面更新时会很麻烦(如果你不打算更新,当我没说)。因此,引入自定义的jscss文件,这里一般在自己的主题里面都是可以找的相应位置的,不动源代码,才是最稳妥的办法。

解决方案

一般方法

因此,我在.\Blog\node_modules\hexo-theme-fluid\source\目录下自定义了一个文件夹custom,把自定义的jscss及其他文件(夹)统一放在该文件夹下,方便管理、查找、修改和备份。

可以在博客根目录.\Blog下的主题配置文件_config.fluid.yml中的custom_jscustom_css中分别引入自定义的jscss文件:

1
2
3
4
5
6
7
8
9
10
11
# 指定自定义 .js 文件路径,支持列表;路径是相对 source 目录,如 /js/custom.js 对应存放目录 source/js/custom.js
# Specify the path of your custom js file, support list. The path is relative to the source directory, such as `/js/custom.js` corresponding to the directory `source/js/custom.js`
custom_js:
- /custom/live2d/live2d-widget/autoload.js
- /custom/live2d/jquery.min.js

# 指定自定义 .css 文件路径,用法和 custom_js 相同
# The usage is the same as custom_js
custom_css:
- /custom/Markdown/macpanel
- /custom/live2d/font-awesome.min.css

Hexo注入代码

方法介绍

Hexo注入器是 Hexo 5 版本自身加入的一项新功能,所以在所有 Hexo 主题都是支持这个功能的。注入器可以将 HTML 片段注入生成页面的 <head><body> 节点中。

编写注入代码,需要在博客的根目录.\Blog\下创建scripts文件夹,然后在里面任意命名创建一个js文件即可。例如创建一个 ./blog/scripts/example.js,内容为:

1
hexo.extend.injector.register('body_end', '<script src="/jquery.js"></script>', 'default');

上述代码会在生成的页面 </body> 注入加载 jquery.js 的代码。

Hexo Fluid主题也提供了一套注入代码功能,相较于 Hexo 注入功能更细致更丰富,并且支持注入 ejs 代码。这里仅做介绍,不具体展开。关于Hexo和Fluid注入代码的方法和参数含义,请见进阶用法 | Hexo Fluid 用户手册

因此对于不得不修改主题源代码的地方,我会尽量选择注入代码的方法而不是直接修改源代码文件。

最后,对于找不到别的办法,只能修改主题源代码的地方,我会做好备注,方便在主题更新后再重新在对应位置复现一遍更改。

HTML标签小知识

问:JavaScript脚本放在<head><body><header><footer>的区别。

以下是ChatGPT的回答:

在HTML文档中,JavaScript脚本可以放在<head><body><header><footer>等不同的部分,各自的位置会对脚本的加载和执行产生不同的影响:

  1. 放在<head>:
    • 当脚本放在<head>中时,它们会在文档的其余部分(即<body>内的内容)被解析之前加载和执行。
    • 这意味着如果脚本较大或加载时间较长,它可能会延迟网页内容的显示,因为浏览器会先执行头部的脚本。
    • 通常,如果脚本不依赖于页面中的任何DOM元素,或者需要尽早加载(如字符集声明或某些配置脚本),则可能会放在<head>中。
  2. 放在<body>:
    • 将脚本放在<body>的末尾,即页面内容之后,是一种常见和推荐的做法,特别是如果脚本需要操作DOM元素的时候。
    • 这样做可以确保在脚本执行时,页面中的DOM元素已经被解析和加载,减少了因为DOM尚未加载完毕导致的错误。
    • 此外,这也允许浏览器更快地渲染页面的可视内容,因为脚本的加载和执行是在页面内容加载完毕之后进行的。
  3. 放在<header><footer>:
    • <header><footer>标签是<body>标签内的一部分,因此将脚本放在这些位置本质上是将它们放在<body>的某个位置。
    • 将脚本放在<header><footer>中并没有特别的技术优势,通常取决于脚本的作用和需要执行的时机。例如,你可能希望在页面头部区域加载的导航脚本放在<header>标签内,或者在页面底部放置与页脚相关的脚本。

其他注意事项:

  • 异步和延迟加载:无论脚本放在哪里,使用asyncdefer属性可以控制脚本的加载和执行方式。async用于异步加载脚本,而defer会延迟执行脚本直到文档解析完成。
  • 外部脚本和模块:对于外部脚本或模块,放置的位置同样遵循上述逻辑,但可能更依赖于脚本的功能和预期的加载行为。

综上所述,将脚本放在哪里取决于脚本的作用、对页面加载性能的影响,以及对DOM元素的依赖情况。通常推荐将影响DOM的脚本放在<body>的底部以提高页面加载速度和用户体验。

补救措施

另外,发现提交到GitHub上的文件夹,即.\Blog\.deploy_git里面存有.\Blog\node_modules\hexo-theme-fluid\即主题文件夹下的配置文件等内容。因此,如果在更新主题前没有及时备份,导致更新主题后之前在主题文件夹下自定义和修改过的文件(夹)被覆盖掉。可以在更新主题后不要急着hexo clean && hexo g && hexo d,这之前可以在.\Blog\.deploy_git里面或者GitHub上对应的的存储库中找到之前的文件(夹)。

鼠标点击特效

最终效果如下图所示:

摸索过程

摸索过程有些许参考价值,方便后期自定义

初步尝试

自己首先根据鼠标点击特效的参考链接-1的说明,在.\Blog\node_modules\hexo-theme-fluid\source\custom\目录下新建文件夹fireworks,在该文件夹内新建文件fireworks.js并写入以下内容:

1
"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)};

然后,在.\Blog\node_modules\hexo-theme-fluid\layout目录下的layout.ejs文件末尾加入以下内容:

1
2
3
<canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
<script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>
<script type="text/javascript" src="/custom/fireworks/fireworks.js"></script>

注意:

  1. 这里是将这三行语句直接加入到了layout.ejs文件末尾,而没有根据鼠标点击特效的参考链接-1的说明加入到文件的<body>块中。因为实际测试下加入到<body>块中博客显示有些问题,而直接加到文件末尾反而功能正常。

  2. 鼠标点击特效的参考链接-1的原语句如下:

    1
    2
    3
    4
    5
       {% if theme.fireworks %}
    <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas>
    <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>
    <script type="text/javascript" src="/js/fireworks.js"></script>
    {% endif %}

    然后,在./Blog/_config.fluid.yml文件末尾加入:

    1
    fireworks: true

    这应该为了后期统一管理,方便一键开启鼠标点击效果。实际自己没有进行这一步

最后,在./Blog文件夹下打开Git Bash,输入以下命令部署博客:

1
hexo clean && hexo g && hexo d

接着输入以下命令查看部署:

1
hexo s

浏览器输入 http://localhost:4000 就可以打开新部署的网页,预览博客。过段时间后,自己的博客应该就会同步更新了。

遇到的问题和解决

在预览博客时,自己发现的一个主要问题是鼠标点击效果会被正文挡住,我猜应该是图层上下排列顺序的问题,不过自己对这方面没有涉猎,只是凭空猜想(现在自己猜测应该是layout.ejs文件末尾新增三行语句中z-index参数值的问题,该值越大所在的图层就越高,最大值是?所以其实改大z-index参数值就可以解决该问题了)。因此,又在网上找到了两个参考链接,鼠标点击特效的参考链接-2的效果如下图所示:

鼠标点击特效的参考链接-3的效果如下图所示:

实际点击效果可以点击参考链接到博主的博客上点点看,这里截得有点粗糙。

3个参考链接实现步骤基本一致,鼠标点击特效的参考链接-2鼠标点击特效的参考链接-3定义的是一个fireworks.min.js文件,内容分别如下:

1
2
// 鼠标点击特效的参考链接-2的fireworks.min.js文件内容
!function(){function i(){scale=window.devicePixelRatio||1,n.width=window.innerWidth*scale,n.height=window.innerHeight*scale,u.scale(scale,scale)}function r(n){-1<(n=d.indexOf(n))&&d.splice(n,1)}function e(n,e){i();var t,a=function(n,e){for(var t=[],a=0;a<24;a++){var i=function(n,e){var t={};return t.x=n,t.y=e,t.color=c[anime.random(0,c.length-1)],t.radius=anime.random(o(),2*o()),t.draw=function(){u.beginPath(),u.arc(t.x,t.y,t.radius,0,2*Math.PI,!0),u.fillStyle=t.color,u.fill()},t}(n,e);t.push(i)}return t}(n,e),n=(e=e,(t={}).x=n,t.y=e,t.color=c[anime.random(0,c.length-1)],t.color="#FFF",t.radius=0,t.alpha=1,t.lineWidth=6,t.draw=function(){u.globalAlpha=t.alpha,u.beginPath(),u.arc(t.x,t.y,t.radius,0,2*Math.PI,!0),u.lineWidth=t.lineWidth,u.strokeStyle=t.color,u.stroke(),u.globalAlpha=1},t),e=anime({targets:a,x:function(n){return n.x+anime.random(-200,200)},y:function(n){return n.y+anime.random(-200,200)},radius:0,duration:function(){return anime.random(1200,1800)},easing:"easeOutExpo",complete:r}),a=anime({targets:n,radius:function(){return anime.random(8.75*o(),11.25*o())},lineWidth:0,alpha:{value:0,easing:"linear",duration:function(){return anime.random(400,600)}},duration:function(){return anime.random(1200,1800)},easing:"easeOutExpo",complete:r});d.push(e),d.push(a)}var t,a,o=function(){return parseFloat(getComputedStyle(document.documentElement).fontSize)},n=document.getElementById("fireworks"),u=n.getContext("2d"),d=[],c=["#ff324a","#31ffa6","#206eff","#ffff99"];anime({duration:1/0,update:function(){u.clearRect(0,0,n.width,n.height),d.forEach(function(n){n.animatables.forEach(function(n){n.target.draw()})})}}),document.addEventListener("mousedown",function(n){t=n.clientX,a=n.clientY,e(t,a)},!1),window.addEventListener("resize",i,!1)}();
1
2
// 鼠标点击特效的参考链接-3的fireworks.min.js文件内容
!function () { var we = document.createElement("canvas"); we.style.cssText = "position:fixed;top:0;left:0;pointer-events:none;z-index:9999999"; document.body.appendChild(we); var Ce = we.getContext("2d"); var xe = 30; var ke = 0; var Le = 0; var Ee = "click"; var Te = ["rgba(255,182,185,.9)", "rgba(250,227,217,.9)", "rgba(187,222,214,.9)", "rgba(138,198,209,.9)"]; function Me() { we.width = window.innerWidth * 2; we.height = window.innerHeight * 2; we.style.width = window.innerWidth + "px"; we.style.height = window.innerHeight + "px"; we.getContext("2d").scale(2, 2) } function Ne(e) { ke = e.clientX || e.touches && e.touches[0].clientX; Le = e.clientY || e.touches && e.touches[0].clientY } function Oe(e) { var t = anime.random(0, 360) * Math.PI / 180; var n = anime.random(50, 180); var a = [-1, 1][anime.random(0, 1)] * n; return { x: e.x + a * Math.cos(t), y: e.y + a * Math.sin(t) } } function je(e, t) { var n = {}; n.x = e; n.y = t; n.color = Te[anime.random(0, Te.length - 1)]; n.radius = anime.random(16, 32); n.endPos = Oe(n); n.draw = function () { Ce.beginPath(); Ce.arc(n.x, n.y, n.radius, 0, 2 * Math.PI, true); Ce.fillStyle = n.color; Ce.fill() }; return n } function Ie(e, t) { var n = {}; n.x = e; n.y = t; n.color = "#FFF"; n.radius = .1; n.alpha = .5; n.lineWidth = 6; n.draw = function () { Ce.globalAlpha = n.alpha; Ce.beginPath(); Ce.arc(n.x, n.y, n.radius, 0, 2 * Math.PI, true); Ce.lineWidth = n.lineWidth; Ce.strokeStyle = n.color; Ce.stroke(); Ce.globalAlpha = 1 }; return n } function He(e) { for (var t = 0; t < e.animatables.length; t++) { e.animatables[t].target.draw() } } function qe(e, t) { var n = Ie(e, t); var a = []; for (var i = 0; i < xe; i++) { a.push(je(e, t)) } anime.timeline().add({ targets: a, x: function (e) { return e.endPos.x }, y: function (e) { return e.endPos.y }, radius: .1, duration: anime.random(1200, 1800), easing: "easeOutExpo", update: He }).add({ targets: n, radius: anime.random(80, 160), lineWidth: 0, alpha: { value: 0, easing: "linear", duration: anime.random(600, 800) }, duration: anime.random(1200, 1800), easing: "easeOutExpo", update: He }, 0) } var Ae = anime({ duration: Infinity, update: function () { Ce.clearRect(0, 0, we.width, we.height) } }); document.addEventListener(Ee, function (e) { Ae.play(); Ne(e); qe(ke, Le) }, false); Me(); window.addEventListener("resize", Me, false); }();

鼠标点击特效的参考链接-2鼠标点击特效的参考链接-3layout.ejs文件新增的内容一致,为:

1
2
3
4
5
6
<canvas
id="fireworks"
style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 32767"
></canvas>
<script src="https://cdn.staticfile.org/animejs/3.2.1/anime.min.js"></script>
<script src="/custom/fireworks/fireworks.min.js"></script>

由此可以看出,鼠标点击展示出的效果是定义在fireworks.min.js文件里的,我猜,比如rgba定义了展示出的颜色,鼠标点击特效的参考链接-1定义的fireworks.js里的colors同理。不过,自己没有学过这方面的语法,目前也没有深究的打算。如果想自定义的话,可以从这方面下手。

按照鼠标点击特效的参考链接-2说明的文件内容,自己实践成功了,鼠标点击特效不会被正文挡住。但是,自己还是更喜欢鼠标点击特效的参考链接-1展示出的效果,鼠标点击特效的参考链接-2爆炸出的球球数量有点少,鼠标点击特效的参考链接-3的颜色有点淡。如上一段所说,因为不了解语法,自己没有准备深究fireworks.jsfireworks.min.js文件里的内容,也是感觉被挡住的图层问题是出现在对layout.ejs文件新增的三行语句上的。

最终,通过参考鼠标点击特效的参考链接-2鼠标点击特效的参考链接-3layout.ejs文件新增的三行语句,经过自己的多次尝试,发现将鼠标点击特效的参考链接-1layout.ejs文件新增的三行语句修改如下可以正常实现鼠标点击效果而不会被正文挡住了:

1
2
3
4
5
6
<canvas
class="fireworks"
style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 32767"
></canvas>
<script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>
<script type="text/javascript" src="/custom/fireworks/fireworks.js"></script>

改为本地引用

可以注意到,对layout.ejs文件新增的三行语句的第二句中包含有一个网址,我猜应该是引用了这个网址里的内容。输入网址进入一看,原来引用的上传到网上的anime.min.js的内容(内容太多,见最终结论部分,这里就不展示了)。有过前车之鉴,所以自己向来怕上传到网上的而不是保存在自己本地的东西指不定哪一天会因为什么原因而消失,到时候追悔莫及。所以,自己尝试把anime.min.js同样保存在.\Blog\node_modules\hexo-theme-fluid\source\custom\fireworks\目录下,并参考layout.ejs文件的第三行语句把第二行对anime.min.js文件网址的引用改成了对本地的引用:

1
2
3
4
5
6
<canvas
class="fireworks"
style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 32767"
></canvas>
<script type="text/javascript" src="/custom/fireworks/anime.min.js"></script>
<script type="text/javascript" src="/custom/fireworks/fireworks.js"></script>

Hexo注入代码

上述通过直接在layout.ejs文件中新增的语句的方法直接修改了源文件,应该是尽量避免的。因此,在了解到可以通过注入代码的方法来避免修改源代码文件后,我进行了尝试,成功的实践为:

  1. .\Blog\node_modules\hexo-theme-fluid\source\custom\fireworks\目录下,新建文件fireworks.ejs,文件内容如下:

    1
    2
    3
    4
    5
    6
    <canvas
    class="fireworks"
    style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 32767"
    ></canvas>
    <script type="text/javascript" src="/custom/fireworks/anime.min.js"></script>
    <script type="text/javascript" src="/custom/fireworks/fireworks.js"></script>

    注意这里的路径是在.\Blog\node_modules\hexo-theme-fluid\source\下的路径。

  2. .\Blog\scripts目录下新建文件fireworks.js(没有scripts文件夹则自己新建一个就行),文件内容如下:

    1
    2
    3
    hexo.extend.filter.register('theme_inject', function(injects) {
    injects.bodyEnd.file('fireworks', 'node_modules/hexo-theme-fluid/source/custom/fireworks/fireworks.ejs', { key: 'value' }, { cache: true }, -1);
    });

    注意这里的路径是在.\Blog\下的路径。

最终博客的效果是正常的。摸索的过程到这里也就告一段落了。

最终结论

实现步骤:

  1. .\Blog\node_modules\hexo-theme-fluid\source\custom\目录下新建文件夹fireworks,在该文件夹内新建文件fireworks.js并写入以下内容:

    1
    "use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)};
  2. 在这个目录下新建文件anime.min.js并写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /*
    2017 Julian Garnier
    Released under the MIT license
    */
    var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(e,r,p){if(p.get||p.set)throw new TypeError("ES3 does not support getters and setters.");e!=Array.prototype&&e!=Object.prototype&&(e[r]=p.value)};$jscomp.getGlobal=function(e){return"undefined"!=typeof window&&window===e?e:"undefined"!=typeof global&&null!=global?global:e};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
    $jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(e){return $jscomp.SYMBOL_PREFIX+(e||"")+$jscomp.symbolCounter_++};
    $jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var e=$jscomp.global.Symbol.iterator;e||(e=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[e]&&$jscomp.defineProperty(Array.prototype,e,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(e){var r=0;return $jscomp.iteratorPrototype(function(){return r<e.length?{done:!1,value:e[r++]}:{done:!0}})};
    $jscomp.iteratorPrototype=function(e){$jscomp.initSymbolIterator();e={next:e};e[$jscomp.global.Symbol.iterator]=function(){return this};return e};$jscomp.array=$jscomp.array||{};$jscomp.iteratorFromArray=function(e,r){$jscomp.initSymbolIterator();e instanceof String&&(e+="");var p=0,m={next:function(){if(p<e.length){var u=p++;return{value:r(u,e[u]),done:!1}}m.next=function(){return{done:!0,value:void 0}};return m.next()}};m[Symbol.iterator]=function(){return m};return m};
    $jscomp.polyfill=function(e,r,p,m){if(r){p=$jscomp.global;e=e.split(".");for(m=0;m<e.length-1;m++){var u=e[m];u in p||(p[u]={});p=p[u]}e=e[e.length-1];m=p[e];r=r(m);r!=m&&null!=r&&$jscomp.defineProperty(p,e,{configurable:!0,writable:!0,value:r})}};$jscomp.polyfill("Array.prototype.keys",function(e){return e?e:function(){return $jscomp.iteratorFromArray(this,function(e){return e})}},"es6-impl","es3");var $jscomp$this=this;
    (function(e,r){"function"===typeof define&&define.amd?define([],r):"object"===typeof module&&module.exports?module.exports=r():e.anime=r()})(this,function(){function e(a){if(!h.col(a))try{return document.querySelectorAll(a)}catch(c){}}function r(a,c){for(var d=a.length,b=2<=arguments.length?arguments[1]:void 0,f=[],n=0;n<d;n++)if(n in a){var k=a[n];c.call(b,k,n,a)&&f.push(k)}return f}function p(a){return a.reduce(function(a,d){return a.concat(h.arr(d)?p(d):d)},[])}function m(a){if(h.arr(a))return a;
    h.str(a)&&(a=e(a)||a);return a instanceof NodeList||a instanceof HTMLCollection?[].slice.call(a):[a]}function u(a,c){return a.some(function(a){return a===c})}function C(a){var c={},d;for(d in a)c[d]=a[d];return c}function D(a,c){var d=C(a),b;for(b in a)d[b]=c.hasOwnProperty(b)?c[b]:a[b];return d}function z(a,c){var d=C(a),b;for(b in c)d[b]=h.und(a[b])?c[b]:a[b];return d}function T(a){a=a.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,function(a,c,d,k){return c+c+d+d+k+k});var c=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(a);
    a=parseInt(c[1],16);var d=parseInt(c[2],16),c=parseInt(c[3],16);return"rgba("+a+","+d+","+c+",1)"}function U(a){function c(a,c,b){0>b&&(b+=1);1<b&&--b;return b<1/6?a+6*(c-a)*b:.5>b?c:b<2/3?a+(c-a)*(2/3-b)*6:a}var d=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(a);a=parseInt(d[1])/360;var b=parseInt(d[2])/100,f=parseInt(d[3])/100,d=d[4]||1;if(0==b)f=b=a=f;else{var n=.5>f?f*(1+b):f+b-f*b,k=2*f-n,f=c(k,n,a+1/3),b=c(k,n,a);a=c(k,n,a-1/3)}return"rgba("+
    255*f+","+255*b+","+255*a+","+d+")"}function y(a){if(a=/([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(a))return a[2]}function V(a){if(-1<a.indexOf("translate")||"perspective"===a)return"px";if(-1<a.indexOf("rotate")||-1<a.indexOf("skew"))return"deg"}function I(a,c){return h.fnc(a)?a(c.target,c.id,c.total):a}function E(a,c){if(c in a.style)return getComputedStyle(a).getPropertyValue(c.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase())||"0"}function J(a,c){if(h.dom(a)&&
    u(W,c))return"transform";if(h.dom(a)&&(a.getAttribute(c)||h.svg(a)&&a[c]))return"attribute";if(h.dom(a)&&"transform"!==c&&E(a,c))return"css";if(null!=a[c])return"object"}function X(a,c){var d=V(c),d=-1<c.indexOf("scale")?1:0+d;a=a.style.transform;if(!a)return d;for(var b=[],f=[],n=[],k=/(\w+)\((.+?)\)/g;b=k.exec(a);)f.push(b[1]),n.push(b[2]);a=r(n,function(a,b){return f[b]===c});return a.length?a[0]:d}function K(a,c){switch(J(a,c)){case "transform":return X(a,c);case "css":return E(a,c);case "attribute":return a.getAttribute(c)}return a[c]||
    0}function L(a,c){var d=/^(\*=|\+=|-=)/.exec(a);if(!d)return a;var b=y(a)||0;c=parseFloat(c);a=parseFloat(a.replace(d[0],""));switch(d[0][0]){case "+":return c+a+b;case "-":return c-a+b;case "*":return c*a+b}}function F(a,c){return Math.sqrt(Math.pow(c.x-a.x,2)+Math.pow(c.y-a.y,2))}function M(a){a=a.points;for(var c=0,d,b=0;b<a.numberOfItems;b++){var f=a.getItem(b);0<b&&(c+=F(d,f));d=f}return c}function N(a){if(a.getTotalLength)return a.getTotalLength();switch(a.tagName.toLowerCase()){case "circle":return 2*
    Math.PI*a.getAttribute("r");case "rect":return 2*a.getAttribute("width")+2*a.getAttribute("height");case "line":return F({x:a.getAttribute("x1"),y:a.getAttribute("y1")},{x:a.getAttribute("x2"),y:a.getAttribute("y2")});case "polyline":return M(a);case "polygon":var c=a.points;return M(a)+F(c.getItem(c.numberOfItems-1),c.getItem(0))}}function Y(a,c){function d(b){b=void 0===b?0:b;return a.el.getPointAtLength(1<=c+b?c+b:0)}var b=d(),f=d(-1),n=d(1);switch(a.property){case "x":return b.x;case "y":return b.y;
    case "angle":return 180*Math.atan2(n.y-f.y,n.x-f.x)/Math.PI}}function O(a,c){var d=/-?\d*\.?\d+/g,b;b=h.pth(a)?a.totalLength:a;if(h.col(b))if(h.rgb(b)){var f=/rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(b);b=f?"rgba("+f[1]+",1)":b}else b=h.hex(b)?T(b):h.hsl(b)?U(b):void 0;else f=(f=y(b))?b.substr(0,b.length-f.length):b,b=c&&!/\s/g.test(b)?f+c:f;b+="";return{original:b,numbers:b.match(d)?b.match(d).map(Number):[0],strings:h.str(a)||c?b.split(d):[]}}function P(a){a=a?p(h.arr(a)?a.map(m):m(a)):[];return r(a,
    function(a,d,b){return b.indexOf(a)===d})}function Z(a){var c=P(a);return c.map(function(a,b){return{target:a,id:b,total:c.length}})}function aa(a,c){var d=C(c);if(h.arr(a)){var b=a.length;2!==b||h.obj(a[0])?h.fnc(c.duration)||(d.duration=c.duration/b):a={value:a}}return m(a).map(function(a,b){b=b?0:c.delay;a=h.obj(a)&&!h.pth(a)?a:{value:a};h.und(a.delay)&&(a.delay=b);return a}).map(function(a){return z(a,d)})}function ba(a,c){var d={},b;for(b in a){var f=I(a[b],c);h.arr(f)&&(f=f.map(function(a){return I(a,
    c)}),1===f.length&&(f=f[0]));d[b]=f}d.duration=parseFloat(d.duration);d.delay=parseFloat(d.delay);return d}function ca(a){return h.arr(a)?A.apply(this,a):Q[a]}function da(a,c){var d;return a.tweens.map(function(b){b=ba(b,c);var f=b.value,e=K(c.target,a.name),k=d?d.to.original:e,k=h.arr(f)?f[0]:k,w=L(h.arr(f)?f[1]:f,k),e=y(w)||y(k)||y(e);b.from=O(k,e);b.to=O(w,e);b.start=d?d.end:a.offset;b.end=b.start+b.delay+b.duration;b.easing=ca(b.easing);b.elasticity=(1E3-Math.min(Math.max(b.elasticity,1),999))/
    1E3;b.isPath=h.pth(f);b.isColor=h.col(b.from.original);b.isColor&&(b.round=1);return d=b})}function ea(a,c){return r(p(a.map(function(a){return c.map(function(b){var c=J(a.target,b.name);if(c){var d=da(b,a);b={type:c,property:b.name,animatable:a,tweens:d,duration:d[d.length-1].end,delay:d[0].delay}}else b=void 0;return b})})),function(a){return!h.und(a)})}function R(a,c,d,b){var f="delay"===a;return c.length?(f?Math.min:Math.max).apply(Math,c.map(function(b){return b[a]})):f?b.delay:d.offset+b.delay+
    b.duration}function fa(a){var c=D(ga,a),d=D(S,a),b=Z(a.targets),f=[],e=z(c,d),k;for(k in a)e.hasOwnProperty(k)||"targets"===k||f.push({name:k,offset:e.offset,tweens:aa(a[k],d)});a=ea(b,f);return z(c,{children:[],animatables:b,animations:a,duration:R("duration",a,c,d),delay:R("delay",a,c,d)})}function q(a){function c(){return window.Promise&&new Promise(function(a){return p=a})}function d(a){return g.reversed?g.duration-a:a}function b(a){for(var b=0,c={},d=g.animations,f=d.length;b<f;){var e=d[b],
    k=e.animatable,h=e.tweens,n=h.length-1,l=h[n];n&&(l=r(h,function(b){return a<b.end})[0]||l);for(var h=Math.min(Math.max(a-l.start-l.delay,0),l.duration)/l.duration,w=isNaN(h)?1:l.easing(h,l.elasticity),h=l.to.strings,p=l.round,n=[],m=void 0,m=l.to.numbers.length,t=0;t<m;t++){var x=void 0,x=l.to.numbers[t],q=l.from.numbers[t],x=l.isPath?Y(l.value,w*x):q+w*(x-q);p&&(l.isColor&&2<t||(x=Math.round(x*p)/p));n.push(x)}if(l=h.length)for(m=h[0],w=0;w<l;w++)p=h[w+1],t=n[w],isNaN(t)||(m=p?m+(t+p):m+(t+" "));
    else m=n[0];ha[e.type](k.target,e.property,m,c,k.id);e.currentValue=m;b++}if(b=Object.keys(c).length)for(d=0;d<b;d++)H||(H=E(document.body,"transform")?"transform":"-webkit-transform"),g.animatables[d].target.style[H]=c[d].join(" ");g.currentTime=a;g.progress=a/g.duration*100}function f(a){if(g[a])g[a](g)}function e(){g.remaining&&!0!==g.remaining&&g.remaining--}function k(a){var k=g.duration,n=g.offset,w=n+g.delay,r=g.currentTime,x=g.reversed,q=d(a);if(g.children.length){var u=g.children,v=u.length;
    if(q>=g.currentTime)for(var G=0;G<v;G++)u[G].seek(q);else for(;v--;)u[v].seek(q)}if(q>=w||!k)g.began||(g.began=!0,f("begin")),f("run");if(q>n&&q<k)b(q);else if(q<=n&&0!==r&&(b(0),x&&e()),q>=k&&r!==k||!k)b(k),x||e();f("update");a>=k&&(g.remaining?(t=h,"alternate"===g.direction&&(g.reversed=!g.reversed)):(g.pause(),g.completed||(g.completed=!0,f("complete"),"Promise"in window&&(p(),m=c()))),l=0)}a=void 0===a?{}:a;var h,t,l=0,p=null,m=c(),g=fa(a);g.reset=function(){var a=g.direction,c=g.loop;g.currentTime=
    0;g.progress=0;g.paused=!0;g.began=!1;g.completed=!1;g.reversed="reverse"===a;g.remaining="alternate"===a&&1===c?2:c;b(0);for(a=g.children.length;a--;)g.children[a].reset()};g.tick=function(a){h=a;t||(t=h);k((l+h-t)*q.speed)};g.seek=function(a){k(d(a))};g.pause=function(){var a=v.indexOf(g);-1<a&&v.splice(a,1);g.paused=!0};g.play=function(){g.paused&&(g.paused=!1,t=0,l=d(g.currentTime),v.push(g),B||ia())};g.reverse=function(){g.reversed=!g.reversed;t=0;l=d(g.currentTime)};g.restart=function(){g.pause();
    g.reset();g.play()};g.finished=m;g.reset();g.autoplay&&g.play();return g}var ga={update:void 0,begin:void 0,run:void 0,complete:void 0,loop:1,direction:"normal",autoplay:!0,offset:0},S={duration:1E3,delay:0,easing:"easeOutElastic",elasticity:500,round:0},W="translateX translateY translateZ rotate rotateX rotateY rotateZ scale scaleX scaleY scaleZ skewX skewY perspective".split(" "),H,h={arr:function(a){return Array.isArray(a)},obj:function(a){return-1<Object.prototype.toString.call(a).indexOf("Object")},
    pth:function(a){return h.obj(a)&&a.hasOwnProperty("totalLength")},svg:function(a){return a instanceof SVGElement},dom:function(a){return a.nodeType||h.svg(a)},str:function(a){return"string"===typeof a},fnc:function(a){return"function"===typeof a},und:function(a){return"undefined"===typeof a},hex:function(a){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a)},rgb:function(a){return/^rgb/.test(a)},hsl:function(a){return/^hsl/.test(a)},col:function(a){return h.hex(a)||h.rgb(a)||h.hsl(a)}},A=function(){function a(a,
    d,b){return(((1-3*b+3*d)*a+(3*b-6*d))*a+3*d)*a}return function(c,d,b,f){if(0<=c&&1>=c&&0<=b&&1>=b){var e=new Float32Array(11);if(c!==d||b!==f)for(var k=0;11>k;++k)e[k]=a(.1*k,c,b);return function(k){if(c===d&&b===f)return k;if(0===k)return 0;if(1===k)return 1;for(var h=0,l=1;10!==l&&e[l]<=k;++l)h+=.1;--l;var l=h+(k-e[l])/(e[l+1]-e[l])*.1,n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(.001<=n){for(h=0;4>h;++h){n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(0===n)break;var m=a(l,c,b)-k,l=l-m/n}k=l}else if(0===
    n)k=l;else{var l=h,h=h+.1,g=0;do m=l+(h-l)/2,n=a(m,c,b)-k,0<n?h=m:l=m;while(1e-7<Math.abs(n)&&10>++g);k=m}return a(k,d,f)}}}}(),Q=function(){function a(a,b){return 0===a||1===a?a:-Math.pow(2,10*(a-1))*Math.sin(2*(a-1-b/(2*Math.PI)*Math.asin(1))*Math.PI/b)}var c="Quad Cubic Quart Quint Sine Expo Circ Back Elastic".split(" "),d={In:[[.55,.085,.68,.53],[.55,.055,.675,.19],[.895,.03,.685,.22],[.755,.05,.855,.06],[.47,0,.745,.715],[.95,.05,.795,.035],[.6,.04,.98,.335],[.6,-.28,.735,.045],a],Out:[[.25,
    .46,.45,.94],[.215,.61,.355,1],[.165,.84,.44,1],[.23,1,.32,1],[.39,.575,.565,1],[.19,1,.22,1],[.075,.82,.165,1],[.175,.885,.32,1.275],function(b,c){return 1-a(1-b,c)}],InOut:[[.455,.03,.515,.955],[.645,.045,.355,1],[.77,0,.175,1],[.86,0,.07,1],[.445,.05,.55,.95],[1,0,0,1],[.785,.135,.15,.86],[.68,-.55,.265,1.55],function(b,c){return.5>b?a(2*b,c)/2:1-a(-2*b+2,c)/2}]},b={linear:A(.25,.25,.75,.75)},f={},e;for(e in d)f.type=e,d[f.type].forEach(function(a){return function(d,f){b["ease"+a.type+c[f]]=h.fnc(d)?
    d:A.apply($jscomp$this,d)}}(f)),f={type:f.type};return b}(),ha={css:function(a,c,d){return a.style[c]=d},attribute:function(a,c,d){return a.setAttribute(c,d)},object:function(a,c,d){return a[c]=d},transform:function(a,c,d,b,f){b[f]||(b[f]=[]);b[f].push(c+"("+d+")")}},v=[],B=0,ia=function(){function a(){B=requestAnimationFrame(c)}function c(c){var b=v.length;if(b){for(var d=0;d<b;)v[d]&&v[d].tick(c),d++;a()}else cancelAnimationFrame(B),B=0}return a}();q.version="2.2.0";q.speed=1;q.running=v;q.remove=
    function(a){a=P(a);for(var c=v.length;c--;)for(var d=v[c],b=d.animations,f=b.length;f--;)u(a,b[f].animatable.target)&&(b.splice(f,1),b.length||d.pause())};q.getValue=K;q.path=function(a,c){var d=h.str(a)?e(a)[0]:a,b=c||100;return function(a){return{el:d,property:a,totalLength:N(d)*(b/100)}}};q.setDashoffset=function(a){var c=N(a);a.setAttribute("stroke-dasharray",c);return c};q.bezier=A;q.easings=Q;q.timeline=function(a){var c=q(a);c.pause();c.duration=0;c.add=function(d){c.children.forEach(function(a){a.began=
    !0;a.completed=!0});m(d).forEach(function(b){var d=z(b,D(S,a||{}));d.targets=d.targets||a.targets;b=c.duration;var e=d.offset;d.autoplay=!1;d.direction=c.direction;d.offset=h.und(e)?b:L(e,b);c.began=!0;c.completed=!0;c.seek(d.offset);d=q(d);d.began=!0;d.completed=!0;d.duration>b&&(c.duration=d.duration);c.children.push(d)});c.seek(0);c.reset();c.autoplay&&c.restart();return c};return c};q.random=function(a,c){return Math.floor(Math.random()*(c-a+1))+a};return q});
  3. 在这个目录下新建文件fireworks.ejs并写入以下内容:

    1
    2
    3
    4
    5
    6
    <canvas
    class="fireworks"
    style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 32767"
    ></canvas>
    <script type="text/javascript" src="/custom/fireworks/anime.min.js"></script>
    <script type="text/javascript" src="/custom/fireworks/fireworks.js"></script>

    注意这里的路径是在.\Blog\node_modules\hexo-theme-fluid\source\下的路径。

  4. .\Blog\scripts目录下新建文件fireworks.js(没有scripts文件夹则自己新建一个就行)并写入以下内容:

    1
    2
    3
    hexo.extend.filter.register('theme_inject', function(injects) {
    injects.bodyEnd.file('fireworks', 'node_modules/hexo-theme-fluid/source/custom/fireworks/fireworks.ejs', { key: 'value' }, { cache: true }, -1);
    });

    注意这里的路径是在.\Blog\下的路径。

  5. ./Blog文件夹下打开Git Bash,输入以下命令部署博客:

    1
    hexo clean && hexo g && hexo d

    接着输入以下命令查看部署:

    1
    hexo s

    浏览器输入 http://localhost:4000 就可以打开新部署的网页,预览博客。过段时间后,自己的博客应该就会同步更新了。

行内(反引号``内)代码样式

修改代码颜色

根据请问如何修改行内代码的颜色里的说明,首先,在.\Blog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹Fonts,并在该文件夹内新建样式文件CodeFont.css,内容如下:

1
2
3
4
5
6
7
.markedown-body pre code {
color: rgb(56 160 110);
}

code {
color: rgb(56 160 110);
}

然后,在./Blog目录下修改主题配置文件_config.fluid.yml,找到custom_css配置项,引入刚刚新建的文件:

1
2
custom_css:
- /custom/Fonts/CodeFont.css

完成,部署即可。

修改代码背景

通过尝试修改网页控制台中一个个的颜色,我找到了控制行内代码背景的变量名为inlinecode-bg-color。再在本地文件中检索该变量名,我发现其位于.\Blog\node_modules\hexo-theme-fluid\source\css\_variables\base.styl内的:

1
2
3
// code
$inlinecode-bg-color = rgba(175, 184, 193, .2)
$inlinecode-bg-color-dark = rgba(99, 110, 123, .4)

这里rgbaa我猜测控制的是不透明度。找到了根源,就好修改了。

首先,在.\Blog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹Fonts,并在该文件夹内新建样式文件inlinecode-bg-color.styl,内容如下:

1
2
3
4
:root {
--inlinecode-bg-color: transparent; // rgba(175, 184, 193, .2)
--inlinecode-bg-color-dark: transparent; // rgba(175, 184, 193, .2)
}

注,这里的语法是发现控制台中inlinecode-bg-color变量是被定义在main.css文件内的,进而通过参考main.css文件定义inlinecode-bg-color变量的语法,尝试成功的。

然后,在./Blog目录下修改主题配置文件_config.fluid.yml,找到custom_css配置项,引入刚刚新建的文件:,

1
2
custom_css:
- /custom/Fonts/inlinecode-bg-color

完成,部署即可。

Mac风格代码块

根据替换Mac风格代码块的参考链接里的说明,在.\Blog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹Markdown,并在该文件夹内新建样式文件macpanel.styl,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.highlight
background: #21252b
border-radius: 5px
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, .4)
padding-top: 30px

&::before
background: #fc625d
border-radius: 50%
box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b
content: ' '
height: 12px
left: 12px
margin-top: -20px
position: absolute
width: 12px

./Blog目录下修改主题配置文件_config.fluid.yml,找到custom_css配置项,引入刚刚新建的样式文件(此处引入.styl文件无需加后缀):

1
2
custom_css:
- /custom/Markdown/macpanel

继续修改_config.fluid.yml,找到code.highlightjs配置项,将代码高亮风格修改暗色风格(dark系列),如:

1
2
3
4
5
6
highlightjs:
# 在链接中挑选 style 填入
# Select a style in the link
# See: https://highlightjs.org/static/demo/
style: "github dark dimmed"
style_dark: "dark"

注:如果选用其他代码高亮风格,有可能会造成代码背景是偏白色的,与黑色的Mac栏不搭。

完成。部署即可。

看板娘

此处主要参考引用了两位博主的博客:

添加看板娘的参考链接-1

添加看板娘的参考链接-2

实现步骤

  1. 官方有提供live2d,这里想配置加强版看板娘,需先卸载原始live2d:

    1
    npm uninstall hexo-helper-live2d
  2. .\Blog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹live2d。下载 张书樵大神的项目并解压到该目录,修改项目文件夹名为 live2d-widget

  3. 在博客根目录.\Blog下的主题配置文件_config.fluid.yml中的custom_jscustom_css中加入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    custom_js:
    # live2d的js文件(2)
    - /custom/live2d/live2d-widget/autoload.js
    - //cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js
    # - /custom/live2d/jquery.min.js

    custom_css:
    # live2d的css文件(1)
    - //cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css
    # - /custom/live2d/font-awesome.min.css

    其中的两个cdn链接可以将依赖文件下载到本地然后引入:

    1. .\Blog\node_modules\hexo-theme-fluid\source\custom\live2d目录下新建文件jquery.min.js并复制进jquery.min.js的cdn链接的内容(字符太多,Typora竟然卡死了,这里就不展示了)。

    2. .\Blog\node_modules\hexo-theme-fluid\source\custom\live2d目录下新建文件font-awesome.min.css并复制进font-awesome.min.css的cdn链接的内容。

    3. 在博客根目录.\Blog下的主题配置文件_config.fluid.yml中的custom_jscustom_css中加入:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      custom_js:
      # live2d的js文件(2)
      - /custom/live2d/live2d-widget/autoload.js
      # - //cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js
      - /custom/live2d/jquery.min.js

      custom_css:
      # live2d的css文件(1)
      # - //cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css
      - /custom/live2d/font-awesome.min.css
  4. 修改live2d-widget文件夹中的 autoload.js 文件,将:

    1
    const live2d_path = "https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/";

    改为:

    1
    const live2d_path = '/custom/live2d/live2d-widget/';
  5. 对于Hexo Butterfly主题,添加看板娘的参考链接-2中有提到:打开butterfly主题目录下的\layout\includes目录,点击修改head.pug文件,在文件末尾粘贴:

    1
    script(src='/custom/live2d/live2d-widget/autoload.js')

    对于Hexo Fluid主题没有这一步骤(因为没有找到这个目录和文件)。不用担心,没有发现Hexo Fluid主题一缺失这一步会影响到最终效果。

  6. 指定看板娘API的库。以下两种方法均可:

    1. 引用网上链接。修改 autoload.js 文件,在initWidget函数中,注释掉apiPath(原api地址已不可用)并修改cdnPath,原作者的cdn地址不能换装,可以用Live2d Widget | Akilarの糖果屋中的cdn地址(可换装且模型比较多,添加看板娘的参考链接-1的博主的live2d_api也是在此基础上改的)。

      1
      2
      3
      4
      // 三个cdnPath选一个,分别是原作者,Akilar站长,添加看板娘的参考链接-1的博主的
      cdnPath: "https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/",
      cdnPath: "https://npm.elemecdn.com/akilar-live2dapi@latest/",
      cdnPath: "https://gcore.jsdelivr.net/npm/yzs-live2d_src@1.1.0/",
    2. 这里也可以引用本地文件。将live2d_api放在博客的原网站下(反正免费的CDN加速在国内基本上不得行,基本上需要梯子,所以直接在网站内访问的速度也可以,如果有条件用gitee也可以直接用gitee,不过注意在tips下删掉一些句子)。我选择使用添加看板娘的参考链接-1的博主的live2d_api,具体操作如下:

      1. 下载 添加看板娘的参考链接-1的博主的live2d_api并解压到.\Blog\node_modules\hexo-theme-fluid\source\custom\live2d目录,修改项目文件夹名为 live2d_api

      2. live2d-widget文件夹中的 autoload.js 文件中添加对应的cdnPath

        1
        cdnPath: "/custom/live2d/live2d_api/",
  7. 在博客根目录.\Blog下的主题配置文件_config.fluid.yml中新增语句:

    1
    2
    3
    # 看板娘
    live2d:
    enable: true
  8. 完成!部署即可。

自定义配置

  1. live2d-widget文件夹中,可进行的操作:

    1. 想修改看板娘大小、位置、格式、文本内容等,可查看并修改waifu.csswaifu-tips.jswaifu-tips.json等。

      1. 修改首次加载的模型。修改live2d-widget\src\index.js文件:

        1
        2
        3
        4
        5
        6
        7
        8
        (function initModel() {
        let modelId = localStorage.getItem("modelId"),
        modelTexturesId = localStorage.getItem("modelTexturesId");
        if (modelId === null) {
        // 首次访问加载 指定模型 的 指定材质
        modelId = 3; // 模型 ID
        modelTexturesId = 53; // 材质 ID
        }
      2. live2d-widget\waifu-tips.css文件中:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        /* 修改看板娘的位置:其中人物距离底部的位置margin-bottom */
        #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;
        }
        /* 人物大小 */
        #live2d {
        cursor: grab;
        height: 200px;
        position: relative;
        width: 200px;
        }
        /* 按钮位置 */
        #waifu-tool {
        color: #aaa;
        opacity: 0;
        position: absolute;
        right: 10px;
        top: 20px;
        transition: opacity 1s;
        }
        /* 按钮间距 */
        #waifu-tool span {
        display: block;
        height: 25px;
        text-align: center;
        }
        /* 按钮大小 */
        #waifu-tool svg {
        fill: #7b8c9d;
        cursor: pointer;
        height: 20px;
        transition: fill .3s;
        }
    2. 如果被挡住的话是因为在插件的z-index默认为-1(修改为999就是最顶层,值越大所在的图层就越高),在waifu.css文件中修改即可,调整插件大小也是在这个文件中,修改对应部分的widthheight即可:

      1
      2
      3
      #waifu {
      z-index: 999;
      }
  2. live2d_api文件夹下,可进行的操作有:

    1. 删除模型:

      1. model_list.json中删除对应的models名称,messages对应的是更换看板娘时看板娘会说的话(跟models的顺序对应)。
      2. model文件夹中删除对应名称的文件夹。
    2. 增加模型。

      1. 在将上面两个项目中通过git clone或npm install后,把对应的模型文件夹拉到model文件夹下。
      2. 修改模型文件夹中的那个指定各个文件路径的json文件为index.json,一般这个文件叫name.model.json,一般来说修改到这个步骤的模型文件就已经可用了。
      3. model_list.json文件中的models中添加对应的文件夹名字并添加对应的messages即可。
    3. 修改模型的大小与位置。由于一般拿到的模型可能大小不对,所以也给出了模型的位置及大小的方法:

      1. 打开需要修改的模型的文件夹中的name.model.json文件,添加或修改layout部分:

        1
        2
        3
        4
        5
        "layout": {
        "center_x": 0, // 模型中心对应的横坐标,默认为0
        "center_y": -0.2, // 模型中心对应的纵坐标,默认为0
        "width": 1.5 // 模型的大小,默认为2
        },

        调整到自己需要的比例还挺麻烦的,上面的参数是xiaoski/live2d_models_collection中我调整康娜缩放及位置的参数,应该其他的模型也基本可用(我没试过)。

  3. 等等。

浏览器标签恶搞

根据浏览器标签恶搞的参考链接里的说明,在.\Blog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹LabelPage,并在该文件夹内新建文件LabelPage.js,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 浏览器搞笑标题
var OriginTitle = document.title;
var titleTime;
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
$('[rel="icon"]').attr('href', "/funny.ico");
document.title = '╭(°A°`)╮ 人生若只如初见';
clearTimeout(titleTime);
} else {
$('[rel="icon"]').attr('href', "/img/newtubiao.png");
document.title = '(ฅ>ω<*ฅ) 既见君子 云胡不喜';
titleTime = setTimeout(function() {
document.title = OriginTitle;
}, 2000);
}
});

./Blog目录下修改主题配置文件_config.fluid.yml,找到custom_css配置项,引入刚刚新建的文件:

1
2
custom_css:
- /custom/LabelPage/LabelPage.js

完成。部署即可。

自定义字体

自定义博客字体的方法有两种,分别为引入网络上托管的字体和引入本地字体。引入网络上托管的字体博客加载速度要快些,尤其是别人第一次打开你的博客(浏览器还没有缓存)时,引入本地字体时效性更有保证。还可以为博客分别指定中英文字体,下面一一介绍。

商业用途的话最好使用免费开源的字体。

引入网络上托管的字体

  1. 获取字体链接。

    1. 根据引入网络上托管的字体的参考链接-1的说明,打开 Google Fonts

    2. 搜索思源宋体的英文名:Noto Serif SC

    3. 在列表中选择个人觉得合适的字体样式后,注意此处可根据自己需要选择多个样式。

    4. 点击右上角的购物袋图标 View selected families

    5. 获得link链接。

      1
      <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@200;300;400;500;600;700;900&display=swap" rel="stylesheet">
  2. 引入字体链接。

    1. 根据引入网络上托管的字体的参考链接-2的说明,在./Blog/scripts/文件夹中新建文件font.js并写入以下内容:

      1
      hexo.extend.injector.register('head_end', '<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@200;300;400;500;600;700;900&display=swap" rel="stylesheet">', 'default');

      有的主题可能提供了类似Hexo Fluid主题在./Blog/_config.fluid.yml中指定自定义.js/.css文件路径的功能,可以直接自定义 <head> 节点中的 HTML 内容,例如变量custom_head。如果有这样的话,直接把字体的link链接添加过来应该也可以。

  3. 指定博客的字体。

    1. ./Blog根目录下打开文件_config.fluid.yml,找到一个名为 font-family 的配置项,加入自定义字体的名称:

      1
      2
      3
      4
      5
      6
      7
      # 主题字体配置
      # Font
      font:
      font_size: 18px
      font_family: "Noto Serif SC"
      letter_spacing: 0.02em
      code_font_size: 100%

      此处指定字体的位置适用于Hexo Fluid主题,其它主题可能需要自行找到指定字体的地方。比如将字体英文名称添加到 \yourtheme\source\css\_variables\ 目录下的 base.styl 文件中,具体位置为 font-family:

    2. 到这一步,部署后预览博客,可以博客中除文章里代码块的字体没有被替换后,其它地方都已经替换成功了。

  4. 指定代码块的字体。

    1. 根据如何修改md中代码块的字体?里提供的解决方案,在.\Blog\node_modules\hexo-theme-fluid\source\custom\Fonts文件夹下新建文件CodeFont.css并写入以下内容:

      1
      2
      3
      4
      .markdown-body code, .markdown-body pre {
      font-family: "Noto Serif SC",monospace !important;
      font-size: 100% !important;
      }

      注:这里的custom及其子文件夹为自己新建的文件夹。

    2. ./Blog根目录下打开文件_config.fluid.yml,找到一个名为 custom_css 的配置项,加入刚才自定义文件的路径:

      1
      2
      custom_css:
      - /custom/Fonts/CodeFont.css
  5. 大功告成。部署即可。

引入本地字体

  1. 获取字体。

    google webfonts helpermajodev/google-webfonts-helper(备选链接)搜索Noto Serif SC(思源宋体):

    1. Select charsets: 勾选chinese-simplified和latin。
    2. Select styles: 勾选所有样式。
    3. Copy CSS: 将Customize folder prefix (optional)../fonts/改为/custom/Fonts/fonts/,然后在.\Blog\node_modules\hexo-theme-fluid\source\custom\Fonts文件夹下新建文件noto-serif-sc-v22-chinese-simplified_latin.css并复制进所有内容。
    4. Download files: 下载noto-serif-sc-v22-chinese-simplified_latin.zip压缩包,解压到.\Blog\node_modules\hexo-theme-fluid\source\custom\Fonts文件夹下并重命名为fonts
  2. 指定博客的字体。

    1. ./Blog根目录下打开文件_config.fluid.yml,找到一个名为 font-family 的配置项,加入自定义字体的名称:

      1
      2
      3
      4
      5
      6
      7
      # 主题字体配置
      # Font
      font:
      font_size: 18px
      font_family: "Noto Serif SC"
      letter_spacing: 0.02em
      code_font_size: 100%

      此处指定字体的位置适用于Hexo Fluid主题,其它主题可能需要自行找到指定字体的地方。比如将字体英文名称添加到 \yourtheme\source\css\_variables\ 目录下的 base.styl 文件中,具体位置为 font-family:

    2. 到这一步,部署后预览博客,可以博客中除文章里代码块的字体没有被替换后,其它地方都已经替换成功了。

  3. 指定代码块的字体。

    1. 根据如何修改md中代码块的字体?里提供的解决方案,在.\Blog\node_modules\hexo-theme-fluid\source\custom\Fonts文件夹下新建文件CodeFont.css并写入以下内容:

      1
      2
      3
      4
      .markdown-body code, .markdown-body pre {
      font-family: "Noto Serif SC",monospace !important;
      font-size: 100% !important;
      }

      注:这里的custom及其子文件夹为自己新建的文件夹。

    2. ./Blog根目录下打开文件_config.fluid.yml,找到一个名为 custom_css 的配置项,加入刚才自定义文件的路径:

      1
      2
      custom_css:
      - /custom/Fonts/CodeFont.css
  4. 大功告成。部署即可。

分别指定中英文字体

如果想要自己的博客中分别指定中英文字体,经过自己的尝试,前期获取和引入字体的方法与上文一致,类推即可。只是在指定博客的字体时,需要使用的格式略有不同。

./Blog根目录下打开文件_config.fluid.yml,找到一个名为 font-family 的配置项,加入自定义字体的名称:

1
2
3
4
5
6
7
# 主题字体配置
# Font
font:
font_size: 18px
font_family: "'JetBrains Mono', 'Noto Serif SC'"
letter_spacing: 0.02em
code_font_size: 100%

这里指定的英文字体时JetBrains Mono,中文字体是Noto Serif SC,也就是思源宋体。注意:

  1. 指定的英文字体须在中文字体之前,这是由字体的引用顺序决定的,即排在前面的指定的字体没有适用于该字符的字体时,才会尝试使用后面的指定的字体。
  2. 指定格式是"'JetBrains Mono', 'Noto Serif SC'""JetBrains Mono", "Noto Serif SC"这样会报错的。

自己本想分别设置博客英文字体为JetBrains Mono,中文字体为Noto Serif SC,但是最后发现整体效果没有只使用Noto Serif SC美观。

加载进度条

实现步骤:

  1. 下载CodeByZach/pace: Automatically add a progress bar to your site最新的Releases版本到本地。

  2. .\Blog\node_modules\hexo-theme-fluid\source\custom文件夹下新建文件夹LoadingBar,在该文件夹内放入Releases压缩包根目录的pace.min.js./themes/choice a colour目录下你最喜欢的某个样式的.css文件。每个.css文件都有不同的进度条加载效果,具体效果可以参见加载进度条的效果示意,或者自己多尝试几种,找到最适合搭配在自己博客上的效果。

  3. ./Blog根目录下打开文件_config.fluid.yml,找到名为custom_jscustom_css的配置项,加入刚才自定义文件的路径:

    1
    2
    3
    4
    custom_js:
    - /custom/LoadingBar/pace.min.js
    custom_css:
    - /custom/LoadingBar/pace-theme-corner-indicator.css
  4. 完成。经尝试,无需注入,部署即可。

当然,也可以不使用本地文件,而使用Hexo注入器来注入链接,例如注入链接:

1
2
3
4
<head>
<script src="https://cdn.jsdelivr.net/npm/pace-js@latest/pace.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pace-js@latest/pace-theme-default.min.css">
</head>

这里就不再赘述。具体可以参考Hexo注入代码

顶部阅读进度条

根据超简单实现WEB页面顶部阅读进度条提供的代码,实现步骤为:

  1. .\Blog\node_modules\hexo-theme-fluid\source\custom\目录下新建文件夹ReadingProgress,在该文件夹下新建文件ReadingProgress.css并写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #content_progress {
    /* Positioning */
    position: fixed;
    left: 0;
    top: 0;
    z-index: 32766;
    width: 100%;
    height: 6px;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    border: none;
    background-color: transparent;
    color: #29b7cb;
    }

    #content_progress::-webkit-progress-bar {
    background-color: transparent;
    }

    #content_progress::-webkit-progress-value {
    background-color: #29b7cb;
    }

    #content_progress::-moz-progress-bar {
    background-color: #29b7cb;
    }

    注:可以通过修改ReadingProgress.css文件的内容来自定义进度条样式,如颜色、位置等。

  2. 同样的,还是在该文件夹下新建文件ReadingProgress.js并写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    document.addEventListener('DOMContentLoaded', function () {
    var winHeight = window.innerHeight,
    docHeight = document.documentElement.scrollHeight,
    progressBar = document.querySelector('#content_progress');
    progressBar.max = docHeight - winHeight;
    progressBar.value = window.scrollY;

    document.addEventListener('scroll', function () {
    progressBar.max = document.documentElement.scrollHeight - window.innerHeight;
    progressBar.value = window.scrollY;
    });
    });
  3. ./Blog根目录下打开文件_config.fluid.yml,找到名为custom_jscustom_css的配置项,加入刚才自定义文件的路径:

    1
    2
    3
    4
    custom_js:
    - /custom/ReadingProgress/ReadingProgress.js
    custom_css:
    - /custom/ReadingProgress/ReadingProgress.css
  4. ./Blog/scripts目录下新建文件ReadingProgress.js并写入以下内容:

    1
    hexo.extend.injector.register('head_begin', '<progress id="content_progress" value="0"></progress>', 'post');

    这里使用的是Hexo注入代码的功能,其中第三个参数是注入的页面类型,接受以下值:

    • default: 注入到每个页面(默认值)
    • home: 只注入到主页(is_home()true 的页面)
    • post: 只注入到文章页面(is_post()true 的页面)
    • page: 只注入到独立页面(is_page()true 的页面)
    • archive: 只注入到归档页面(is_archive()true 的页面)
    • category: 只注入到分类页面(is_category()true 的页面)
    • tag: 只注入到标签页面(is_tag()true 的页面)
    • 或是其他自定义 layout 名称,例如在Fluid 主题中 about 对应关于页、links 对应友联页
  5. 完成。部署即可。

评论系统

前言

根据Hexo Fluid主题的推荐,国内用户推荐使用ValineWaline或者twikoo

Valine基于LeanCloud,简洁,但因为自身设计的限制,貌似存在安全问题。自己尝试了Valine,但现在应该是因为DNS污染的问题,部署在博客的评论发不出来。

WalineValine衍生而来,额外增加了服务端和多种功能,安全性得到了更好地保障。不过同样因为DNS污染的问题,直接部署在博客的评论发不出来。但是,Waline可以通过使用自己的域名进行解析的方式来避开这个问题。在自己使用注册的域名进行解析后,成功在博客上部署了Waline。所以,现在正在使用的就是它。Waline设计目标及与Valine的对比

twikooWaline部署成功后就没有进行尝试。

部署Waline评论系统

关于Waline的部署,在它的官网上已经讲得很清楚了,一步一步跟着做就可以。这里就不再赘述,只做几点补充说明(自己在部署过程中踩的坑):

  1. 由于LeanCloud国内版需要为应用额外绑定已备案的域名,同时购买独立IP并完成备案接入。因此建议使用LeanCloud国际版(推荐)

  2. 借用Vercel部署Waline评论系统中的一张图,在Vercel-Settings-Environment Variables中配置环境变量时,”配置三个环境变量 LEAN_ID, LEAN_KEYLEAN_MASTER_KEY ,它们的值分别对应上一步在 LeanCloud 中获得的 APP ID, APP KEY, Master Key“这句话是这个意思:

    最开始自己尝试半天,就两个空怎么填这些信息?

  3. 前面也说了,现在因为DNS污染的问题,直接添加Vercel生成的网址进行部署,评论时会报错Failed to fetch,所以需要自己再注册一个域名进行解析。也就是Waline快速上手中绑定域名(可选)的这一步推荐要做。我是通过阿里云万网额外购买了一个域名,可以参考这篇文章:个人博客第四篇——购买域名。当然,有的博客也提到了可以免费注册域名,例如:使用自定义域名激活Vercel部署的Waline服务。不过自己采用这篇博客提到的方法注册的域名,等了好几天没有等到消息,所以就去购买了一个。

  4. Vercel-Settings-Domains中添加了自己注册的域名后,记得点击顶部的Deployments点击 顶部最新(有Current标志) 的一次部署右侧的Redeploy按钮进行重新部署。

    Redeploy时报错error jsdom@24.1.0: The engine "node" is incompatible with this module. Expected version ">=18". Got "16.20.2"

    解决:

    1. Vercel-对应的项目-Setting-General-Node.js Version:20.x

    2. GitHub-对应的存储库-修改vercel.json

      1
      2
      3
      4
      5
      6
      "builds": [
      {
      "src": "index.js",
      "use": "@vercel/node@3.0.17" // default: 2.5.10
      }
      ],
    3. (修改GitHub存储库后Vercel好像会自动开始响应,重新部署)Vercel-对应的项目-Deployments-点击 顶部最新(有Current标志) 的一次部署右侧的Redeploy按钮进行重新部署。

  5. 如果你和我一样,使用的是Hexo Fluid主题,该主题已经给添加Waline留好了位置,只需在./Blog根目录下打开文件_config.fluid.yml,修改以下内容:

    1
    2
    3
    comments:
    enable: true
    type: waline

    然后接在配置文件中的waline部分就可以了,例如,我的配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    waline:
    serverURL: '你自己注册的域名'
    path: window.location.pathname
    meta: ['nick', 'mail', 'link']
    requiredMeta: ['nick']
    lang: 'zh-CN'
    emoji: ['https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo']
    dark: 'html[data-user-color-scheme="dark"]'
    wordLimit: 0
    pageSize: 10
    reaction: true
    locale:
    placeholder: '花径不曾缘客扫,蓬门今始为君开。'

    跳过Waline快速上手中HTML引入(客户端)这一步就可以了。当然,其它的Hexo主题可能也同样预设了Waline等评论系统的位置,这就需要各自分析了。

  6. (可选)添加图片为输入区域背景。Waline没有提供背景图的配置,需要自行自定义CSS来实现。根据添加图片为输入区域背景的参考链接的说明:

    1. 选择一张你喜欢的图片,放在.\Blog\GitHubBlog\node_modules\hexo-theme-fluid\source\img目录下,例如,重命名为walinebg.webp

    2. .\Blog\GitHubBlog\node_modules\hexo-theme-fluid\source\custom目录下新建文件夹waline,在waline文件夹下新建文件gif.css并写入以下内容:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      /* 添加图片为输入区域背景 */
      #wl-edit {
      background: url("/img/walinebg.webp") 100% 100% no-repeat;
      background-size: contain !important;
      background-repeat: no-repeat !important;
      background-position: right bottom !important;
      }

      #waline-wrap textarea:focus {
      background: url("/img/walinebg.webp") 100% 100% no-repeat;
      background-position-y: 78px !important;
      transition: all 0.25s ease-in-out 0s;
      }
    3. ./Blog/_config.fluid.yml文件中引入新建的.css文件:

      1
      2
      3
      4
      # 指定自定义 .css 文件路径,用法和 custom_js 相同
      # The usage is the same as custom_js
      custom_css:
      - /custom/waline/gif.css
  7. 完成,部署博客即可。

后台管理

  1. 登录LeanCloud国内版LeanCloud国际版(推荐)
  2. 进入应用界面-数据存储-结构化数据-在Comment表中可以看到在博客中的留言信息,在Users表中可以看到所有注册过的用户信息。

当使用评论的登录功能时,第一个注册的用户默认为评论管理系统的管理员。因此用户配置评论系统后需及时申请管理员账号。

更新

当登录你的评论系统(网站)上方横幅提示你可以更新Waline版本时,可以通过部署Waline评论系统的第4步进行更新,也就是在Vercel里Redeploy重新部署一次项目。

添加博客小游戏

添加博客小游戏的参考链接

  1. 获取游戏资源。

    • 下载下来后可以直接使用浏览器打开游戏文件夹内的index.html文件进行预览。
  2. 部署游戏。

    1. 新建页面game

      1
      hexo new page game
    2. (可选)修改和添加./Blog/source/game/目录下的index.md文件内容。

    3. 将下载的游戏文件上传至./Blog/source/game/目录。

    4. (可选)修改游戏文件夹根目录的index.html文件内容,如title等。

    5. (可选)修改游戏文件内链接的http等网址为本地地址。

  3. 博客配置。

    1. 跳过编译。如果你直接生成静态文件,会发现游戏项目内的.html文件被重新编译了,无法玩。所以打开中./Blog/_config.yml文件找到skip_render项,配置如下:

      1
      2
      skip_render: 
      - 'game/*/**'

      这样使./Blog/source/game/目录下所有目录当中的文件不被编译。你也可以根据实际情况更改,如果只想.html文件不被编译,可以配置为:

      1
      2
      skip_render: 
      - 'game/*/*.html'
    2. 导航菜单。

      • Fluid主题可打开./Blog/_config.fluid.yml文件,配置如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        menu:
        - {
        key: '摸鱼',
        link: '/game',
        icon: 'iconfont icon-steam',
        submenu: [
        { key: '魔方', link: '/game/mf' },
        { key: '扫雷', link: '/game/sl' },
        { key: '2048', link: '/game/2048' },
        { key: '五子棋', link: '/game/wzq' },
        { key: '推箱子', link: '/game/txz' }
        ]
        }

        其中:

        • key用来关联./Blog/node_modules/hexo-theme-fluid/languages/*.yml,如不存在关联则显示key本身的值;iconcss class,可以省略;增加 name 可以强制显示指定名称。
        • 有关导航菜单的更多说明,可以参见Fluid用户手册-导航菜单
        • 有关图标使用的更多说明,可以参见Fluid用户手册-图标
      • Matery主题部分菜单导航配置如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        menu:
        Index:
        url: /
        icon: fas fa-home
        Game:
        icon: fa-solid fa-gamepad
        url: /game
        children:
        - name: 魔方
        url: /game/mf
        icon: fa-solid fa-cube
        - name: 扫雷
        url: /game/sl
        icon: fa-brands fa-galactic-republic
        - name: 2048
        url: /game/2048
        icon: fa-brands fa-pix
        - name: 五子棋
        url: /game/wzq
        icon: fa-solid fa-chess-board
        - name: 推箱子
        url: /game/txz
        icon: fa-solid fa-box

        在主题下的 languages/zh-CN.yml 文件中添加:

        1
        Game: 摸鱼
  4. 完成,部署即可。

配置404页

修改默认配置

Hexo Fluid主题包含默认的404页面,可选择修改文件:

  • ./Blog/_config.fluid.yml-page404
  • ./Blog/node_modules/hexo-theme-fluid/layout/404.ejs
  • ./Blog/node_modules/hexo-theme-fluid/languages/*.yml-page404

自定义配置

在博客中,如果链接错误,指向了一个不存在的地址,默认情况下会进入 404 页面。那么如何配置自定义的 404 页面呢?

配置404页的参考链接

  1. ./Blog/source文件夹下新建文件404.html,并写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    ---
    title: 404 - 好巧,你也来吹空调吗?!
    ---

    <!-- <h1 align="center">好巧,你也来吹空调吗?!</h1> -->

    <iframe style="width:100%;" height="800" frameborder="no" src="https://ac.yunyoujun.cn"></iframe>

    这个页面中使用 iframe 引入了一个网页小空调,项目地址 YunYouJun/air-conditioner。也可以根据自己的需要填写其他内容。

  2. 部署即可。可以通过链接 http://localhost:4000/404/ 来查看 404 页面的显示效果。

访问量统计

基于LeanCloud统计博客页面访问次数与访问人数及文章阅读次数。

Hexo Fluid主题目前支持两种数据来源:LeanCloud不蒜子

不蒜子不需要申请账号,直接开启即可,但有时候会响应缓慢拖慢整个页面加载。因此选择使用LeanCloud进行访问量统计。

访问量统计的参考链接

实现步骤

  1. 配置LeanCloud。

    1. 进入LeanCloud国内版LeanCloud国际版(推荐) ,注册账号并完成实名认证和邮箱验证。

      由于LeanCloud国内版需要为应用额外绑定已备案的域名,同时购买独立IP并完成备案接入。因此建议使用LeanCloud国际版(推荐)

    2. 首页-创建应用-应用名称(自定义)-应用描述(自定义)-创建。

    3. 进入应用界面-数据存储-结构化数据-创建Class-Class名称:Counter-ACL权限:无限制-创建。

    4. 进入应用界面-设置-应用凭证-获取AppID与AppKey。

  2. 配置博客。修改./Blog/_config.fluid.yml文件:

    1. 网页统计。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      # 网页访问统计
      # Analysis of website visitors
      web_analytics: # 网页访问统计
      enable: true
      .
      .
      .
      # LeanCloud 计数统计,可用于 PV UV 展示,如果 `web_analytics: enable` 没有开启,PV UV 展示只会查询不会增加
      # LeanCloud count statistics, which can be used for PV UV display. If `web_analytics: enable` is false, PV UV display will only query and not increase
      leancloud:
      app_id: xxx
      app_key: xxx
      # REST API 服务器地址,国际版不填
      # Only the Chinese mainland users need to set
      server_url:
      # 统计页面时获取路径的属性
      # Get the attribute of the page path during statistics
      path: window.location.pathname
      # 开启后不统计本地路径( localhost 与 127.0.0.1 )
      # If true, ignore localhost & 127.0.0.1
      ignore_local: false
    2. 页脚展示 PV 与 UV 统计。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # 展示网站的 PV、UV 统计数
      # Display website PV and UV statistics
      statistics:
      enable: true

      # 统计数据来源,使用 leancloud 需要设置 `web_analytics: leancloud` 中的参数;使用 busuanzi 不需要额外设置,但是有时不稳定,另外本地运行时 busuanzi 显示统计数据很大属于正常现象,部署后会正常
      # Data source. If use leancloud, you need to set the parameter in `web_analytics: leancloud`
      # Options: busuanzi | leancloud
      source: "leancloud"

      这样在博客页面最下面就可以看到访问人数与访问次数。

    3. 显示在文章页大标题下的文章信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      post:
      .
      .
      .
      # 文章标题下方的元信息
      # Meta information below title
      meta:
      .
      .
      .
      # 浏览量计数
      # Number of visits
      views:
      enable: true
      # 统计数据来源
      # Data Source
      # Options: busuanzi | leancloud
      source: "leancloud"

      实现对每篇博客的访问次数进行统计。

  3. 部署即可。

如果参数填写错误或者接口异常,不会显示数据,请在浏览器控制台排查具体原因。

后台管理

登录LeanCloud国内版LeanCloud国际版(推荐),进入应用界面-数据存储-结构化数据-点击创建的Class名称(Counter),即可看到创建了每篇文章阅读的次数以及用户访问博客的次数及人数。

记录文章访问量的唯一标识符是文章的发布日期和文章的标题,因此要确保这两个数值组合的唯一性,如果你更改了这两个数值,会造成文章阅读数值的清零重计。

time字段的数值表示某一篇文章的访问量,其他字段的具体作用可以查阅LeanCloud官方文档,最好不要随意修改。

页脚网站运行时间

页脚网站运行时间的参考链接

  1. .\Blog\node_modules\hexo-theme-fluid\source\custom\RunTime文件夹下新建文件RunTime.js并写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    !(function() {
    /** 计时起始时间,自行修改 **/
    var start = new Date("2020/01/01 00:00:00");

    function update() {
    var now = new Date();
    now.setTime(now.getTime()+250);
    days = (now - start) / 1000 / 60 / 60 / 24;
    dnum = Math.floor(days);
    hours = (now - start) / 1000 / 60 / 60 - (24 * dnum);
    hnum = Math.floor(hours);
    if(String(hnum).length === 1 ){
    hnum = "0" + hnum;
    }
    minutes = (now - start) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes);
    if(String(mnum).length === 1 ){
    mnum = "0" + mnum;
    }
    seconds = (now - start) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds);
    if(String(snum).length === 1 ){
    snum = "0" + snum;
    }
    document.getElementById("timeDate").innerHTML = "本站安全运行&nbsp"+dnum+"&nbsp天";
    document.getElementById("times").innerHTML = hnum + "&nbsp小时&nbsp" + mnum + "&nbsp分&nbsp" + snum + "&nbsp秒";
    }

    update();
    setInterval(update, 1000);
    })();

    备注:

    1. var start = new Date("2020/01/01 00:00:00");一行修改为自己的网站开始时间。
    2. 在调用该js代码之后,会执行每1秒循环调用update()这个函数。
    3. 在这个函数中,比如执行语句document.getElementById("timeDate").innerHTML = "本站安全运行&nbsp"+dnum+"&nbsp天";会将页面内容<span id="timeDate">载入天数...</span>替换,替换为计算出来的天数内容。
  2. 修改./Blog/_config.fluid.yml文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #---------------------------
    # 页脚
    # Footer
    #---------------------------
    footer:
    # 页脚第一行文字的 HTML,建议保留 Fluid 的链接,用于向更多人推广本主题
    # HTML of the first line of the footer, it is recommended to keep the Fluid link to promote this theme to more people
    content: '
    <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
    <i class="iconfont icon-love"></i>
    <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a>
    <div style="font-size: 0.85rem">
    <span id="timeDate">载入天数...</span>
    <span id="times">载入时分秒...</span>
    <script src="/custom/RunTime/RunTime.js"></script>
    </div>
    '

    无需在custom_js配置项中再引入RunTime.js文件。

  3. 部署即可。

礼花打字特效

礼花打字特效的参考链接

  1. .\Blog\node_modules\hexo-theme-fluid\source\custom\typing-effect文件夹下新建文件typing-effect.js并写入以下内容:

    1
    !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.POWERMODE=e():t.POWERMODE=e()}(this,function(){return function(t){function e(n){if(o[n])return o[n].exports;var r=o[n]={exports:{},id:n,loaded:!1};return t[n].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var o={};return e.m=t,e.c=o,e.p="",e(0)}([function(t,e,o){"use strict";function n(t,e){return Math.random()*(e-t)+t}function r(t){if(d.colorful){var e=n(0,360);return"hsla("+n(e-10,e+10)+", 100%, "+n(50,80)+"%, 1)"}return window.getComputedStyle(t).color}function i(){var t,e=document.activeElement;if("TEXTAREA"===e.tagName||"INPUT"===e.tagName&&"text"===e.getAttribute("type")){var n=o(1)(e,e.selectionStart);return t=e.getBoundingClientRect(),{x:n.left+t.left,y:n.top+t.top,color:r(e)}}var i=window.getSelection();if(i.rangeCount){var a=i.getRangeAt(0),d=a.startContainer;return d.nodeType===document.TEXT_NODE&&(d=d.parentNode),t=a.getBoundingClientRect(),{x:t.left,y:t.top,color:r(d)}}return{x:0,y:0,color:"transparent"}}function a(t,e,o){return{x:t,y:e,alpha:1,color:o,velocity:{x:-1+2*Math.random(),y:-3.5+2*Math.random()}}}function d(){for(var t=i(),e=5+Math.round(10*Math.random());e--;)p[f]=a(t.x,t.y,t.color),f=(f+1)%500;if(d.shake){var o=1+2*Math.random(),n=o*(Math.random()>.5?-1:1),r=o*(Math.random()>.5?-1:1);document.body.style.marginLeft=n+"px",document.body.style.marginTop=r+"px",setTimeout(function(){document.body.style.marginLeft="",document.body.style.marginTop=""},75)}}function l(){requestAnimationFrame(l),u.clearRect(0,0,c.width,c.height);for(var t=0;t<p.length;++t){var e=p[t];e.alpha<=.1||(e.velocity.y+=.075,e.x+=e.velocity.x,e.y+=e.velocity.y,e.alpha*=.96,u.globalAlpha=e.alpha,u.fillStyle=e.color,u.fillRect(Math.round(e.x-1.5),Math.round(e.y-1.5),3,3))}}var c=document.createElement("canvas");c.width=window.innerWidth,c.height=window.innerHeight,c.style.cssText="position:fixed;top:0;left:0;pointer-events:none;z-index:999999",window.addEventListener("resize",function(){c.width=window.innerWidth,c.height=window.innerHeight}),document.body.appendChild(c);var u=c.getContext("2d"),p=[],f=0;d.shake=!0,d.colorful=!1,requestAnimationFrame(l),t.exports=d},function(t,e){!function(){function e(t,e,r){var i=r&&r.debug||!1;if(i){var a=document.querySelector("#input-textarea-caret-position-mirror-div");a&&a.parentNode.removeChild(a)}var d=document.createElement("div");d.id="input-textarea-caret-position-mirror-div",document.body.appendChild(d);var l=d.style,c=window.getComputedStyle?getComputedStyle(t):t.currentStyle;l.whiteSpace="pre-wrap","INPUT"!==t.nodeName&&(l.wordWrap="break-word"),l.position="absolute",i||(l.visibility="hidden"),o.forEach(function(t){l[t]=c[t]}),n?t.scrollHeight>parseInt(c.height)&&(l.overflowY="scroll"):l.overflow="hidden",d.textContent=t.value.substring(0,e),"INPUT"===t.nodeName&&(d.textContent=d.textContent.replace(/\s/g,"聽"));var u=document.createElement("span");u.textContent=t.value.substring(e)||".",d.appendChild(u);var p={top:u.offsetTop+parseInt(c.borderTopWidth),left:u.offsetLeft+parseInt(c.borderLeftWidth)};return i?u.style.backgroundColor="#aaa":document.body.removeChild(d),p}var o=["direction","boxSizing","width","height","overflowX","overflowY","borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth","borderStyle","paddingTop","paddingRight","paddingBottom","paddingLeft","fontStyle","fontVariant","fontWeight","fontStretch","fontSize","fontSizeAdjust","lineHeight","fontFamily","textAlign","textTransform","textIndent","textDecoration","letterSpacing","wordSpacing","tabSize","MozTabSize"],n=null!=window.mozInnerScreenX;"undefined"!=typeof t&&"undefined"!=typeof t.exports?t.exports=e:window.getCaretCoordinates=e}()}])}),function(){POWERMODE.colorful=!0,POWERMODE.shake=!1,document.body.addEventListener("input",POWERMODE)}();
  2. 打开./Blog/_config.fluid.yml文件,在custom_js配置项中再引入typing-effec.js文件:

    1
    2
    3
    4
    # 指定自定义 .js 文件路径,支持列表;路径是相对 source 目录,如 /js/custom.js 对应存放目录 source/js/custom.js
    # Specify the path of your custom js file, support list. The path is relative to the source directory, such as `/js/custom.js` corresponding to the directory `source/js/custom.js`
    custom_js:
    - /custom/typing-effect/typing-effect.js
  3. 部署即可。


Hexo博客美化
http://zeyulong.com/posts/3edad46f/
作者
龙泽雨
发布于
2023年12月24日
许可协议