抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

该教程会不定期更新,有任何建议请在评论区留言,或联系邮箱mail@dearxuan.com

本教程的许多内容是"主题无关"的,这意味着即使你不是volantis主题也能使用,但是需要一定的开发和调试能力,这部分内容会在标题和左边的目录里注明"通用",使用方法会写在对应部分的底部

请勿直接使用本站CDN资源,如果流量过大会开启防盗链,若需要使用请自行下载到本地

基础知识 - 小白必看

如果报错 $ is not defined,是因为你没有jQuery,需要自行引用

1
<script src="https://unpkg.com/jquery@3.6.0/dist/jquery.min.js"></script>

某些js需要在网页加载完毕之后再执行,否则会因为某些元素还没加载出来而报错,其中一种方法是给整段代码加上监听,这样能确保里面的代码再整个网页加载完之后再执行

1
2
3
4
let button = document.getElementById("button");
button.onclick = (event) => {
console.log(1);
};
1
2
3
4
5
6
window.addEventListener('load', ()=>{
let button = document.getElementById("button");
button.onclick = (event) => {
console.log(1);
};
});

第二种也是更方便的方法,是把js代码保存在其他文件里,例如custom.js,然后在hexo/source/_volantis/headBegin.ejs(如果没有就自己创建)里引入自己的js代码,注意要添加defer

1
<script defer src="/custom.js"></script>

defer脚本会在网页加载完毕之后执行,且执行顺序与引入的先后顺序一致,所以jQuery这种前置脚本要放在最前面,或者直接不加defer

自定义资源注入

建议在 hexo/source/_volantis/ 目录下注入自定义资源,下面是该目录下文件说明(如果没有可自行创建)

文件名描述
first.stly将样式以硬编码形式直接写入HTML
style.stly延迟异步加载样式
dark.stly暗黑模式地强制覆盖样式
darkVar.stly暗黑模式的CSS变量
headBegin.ejs<head> 标签开头注入自定义内容
headEnd.ejs<head> 标签末尾注入自定义内容
header.ejs导航栏 .nav-main 末尾注入自定义内容
topMeta.ejs侧边栏 #l_side 末尾注入自定义内容
bottomMeta.ejstopMetas 末尾注入自定义内容
postEnd.ejs页尾注入自定义内容
bodyBegin.ejs<body> 标签开头注入自定义内容
bodyEnd.ejs<body> 标签末尾注入自定义内容

一般情况下,在headBegin.ejs里引入js或css,建议把所有自己附加的js全部放到一个文件里,以减少请求次数.实际上你也可以直接在head里写js代码,但是非常不推荐

1
2
3
4
5
6
<script src="https://example.com/custom.js"></script>
<script defer src="https://example.com/custom.js"></script>
<link rel="stylesheet" href="https://example.com/custom.css">
<script>
console.log(1) // 不推荐
</script>

推荐用hexo-extend-theme来覆盖主题本地layout文件

首先全局安装yarn,控制台执行以下代码,已有的可跳过

1
npm install -g yarn

安装hexo-extend-theme,这一步会重装hexo-server模块,如果你以前修改过该模块则需要重新修改

1
yarn add @jiangtj/hexo-extend-theme

hexo/_config.yml里添加配置信息

1
2
3
# layout文件替换
theme_plus:
custom_path: source/_layout

此后如果要修改主题的layout文件,只需要把对应文件复制到source/_layout/文件夹里就可以在不修改主题源码的情况下替换自己的代码,hexo/source/_layout/里的文件会覆盖hexo/themes/volantis/layout/文件夹下的同名文件,这样你也很方便地知道自己改了哪些文件

例如你想要修改hexo/themes/volantis/layout/_plugins/parallax/script.ejs,则只需要把这个文件复制到hexo/source/_layout/_plugins/parallax/script.ejs,此后的修改会直接覆盖源文件,并且不会破坏主题本身

暗黑模式动画(通用)

立即切换

引入以下jscss,注意js代码要加上defer

展开以查看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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function BackTOP() {
$("#btn").hide();
$(function () {
$(window).scroll(function () {
if ($(window).scrollTop() > 50) {
$("#btn").fadeIn(200);
} else {
$("#btn").fadeOut(200);
}
});
$("#btn").click(function () {
$('body,html').animate({
scrollTop: 0
},
500);
return false;
});
});
$(function () {
$("#say").click(function () {
$('body,html').animate({
scrollTop: $('html, body').get(0).scrollHeight
},
500);
return false;
});
})
}

$('#readmode').click(function () {
$('body').toggleClass('read-mode')
})

function SiderMenu() {
$('#main-container').toggleClass('open');
$('.iconflat').css('width', '50px').css('height', '50px');
$('.openNav').css('height', '50px');
$('#main-container,#mo-nav,.openNav').toggleClass('open')
}

function switchNightMode() {
$('<div class="Cuteen_DarkSky"><div class="Cuteen_DarkPlanet"></div></div>').appendTo($("body")), setTimeout(
function () {
(volantis.dark.mode == "dark")
?
($("html").addClass("DarkMode"),
$('#modeicon').attr("xlink:href", "#icon-sun"))
:
($("html").removeClass("DarkMode"),
$('#modeicon').attr("xlink:href", "#icon-_moon")),
setTimeout(function () {
$(".Cuteen_DarkSky").fadeOut(1e3, function () {
$(this).remove()
})
}, 2e3)
}), 50
}

function checkNightMode() {
if ($("html").hasClass("n-f")) {
$("html").removeClass("day");
$("html").addClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-sun")
return;
}
if ($("html").hasClass("d-f")) {
$("html").removeClass("DarkMode");
$("html").addClass("day");
$('#modeicon').attr("xlink:href", "#icon-_moon")
return;
}
if (volantis.dark.mode == "dark") {
$("html").addClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-sun")
} else {
$("html").removeClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-_moon")
}
}
BackTOP();
volantis.dark.push(switchNightMode);
展开以查看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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#RightDownBtn {
position: fixed;
left: 1.875rem;
bottom: 1.875rem;
padding: 0.3125rem 0.625rem;
background: #fff;
border-radius: 0.1875rem;
transition: 0.3s ease all;
z-index: 1;
align-items: flex-end;
flex-direction: column;
display: -moz-flex;
display: flex;
float: right;
}

#RightDownBtn>a,
#RightDownBtn>label {
width: 1.5em;
height: 1.5em;
margin: 0.3125rem 0;
transition: .2s cubic-bezier(.25, .46, .45, .94);
}

/* font color */
.DarkMode #page,
.DarkMode #colophon,
.DarkMode #vcomments .vbtn,
.DarkMode .art-content #archives .al_mon_list .al_mon,
.DarkMode .art-content #archives .al_mon_list span,
.DarkMode body,
.DarkMode .art-content #archives .al_mon_list .al_mon,
.DarkMode .art-content #archives .al_mon_list span,
.DarkMode button,
.DarkMode .art .art-content #archives a,
.DarkMode textarea,
.DarkMode strong,
.DarkMode a,
.DarkMode p,
.DarkMode li,
.DarkMode .label {
color: rgba(255, 255, 255, .6);
}


.DarkMode #page,
.DarkMode body,
.DarkMode #colophon,
.DarkMode #main-container,
.DarkMode #page .yya,
.DarkMode #content,
.DarkMode #contentss,
.DarkMode #footer {
background-color: #292a2d;
}
.DarkMode strong,
.DarkMode img {
filter: brightness(.7);
}

/* sun and noon */
.Cuteen_DarkSky,
.Cuteen_DarkSky:before {
content: "";
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 88888888
}

.Cuteen_DarkSky {
background: linear-gradient(#feb8b0, #fef9db)
}

.Cuteen_DarkSky:before {
transition: 2s ease all;
opacity: 0;
background: linear-gradient(#4c3f6d, #6c62bb, #93b1ed)
}

.DarkMode .Cuteen_DarkSky:before {
opacity: 1
}

.Cuteen_DarkPlanet {
z-index: 99999999;
position: fixed;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
-webkit-animation: CuteenPlanetMove 2s cubic-bezier(.7, 0, 0, 1);
animation: CuteenPlanetMove 2s cubic-bezier(.7, 0, 0, 1);
transform-origin: center bottom
}

@-webkit-keyframes CuteenPlanetMove {
0% {
transform: rotate(0)
}

to {
transform: rotate(360deg)
}
}

@keyframes CuteenPlanetMove {
0% {
transform: rotate(0)
}

to {
transform: rotate(360deg)
}
}

.Cuteen_DarkPlanet:after {
position: absolute;
left: 35%;
top: 40%;
width: 9.375rem;
height: 9.375rem;
border-radius: 50%;
content: "";
background: linear-gradient(#fefefe, #fffbe8)
}
非``volantis``的使用方法

请先确保你的主题支持暗黑模式

首先按上文方法引入css,如果你的主题开发者提供了暗黑模式接口,则以defer方式引入并修改以下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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
function BackTOP() {
$("#btn").hide();
$(function () {
$(window).scroll(function () {
if ($(window).scrollTop() > 50) {
$("#btn").fadeIn(200);
} else {
$("#btn").fadeOut(200);
}
});
$("#btn").click(function () {
$('body,html').animate({
scrollTop: 0
},
500);
return false;
});
});
$(function () {
$("#say").click(function () {
$('body,html').animate({
scrollTop: $('html, body').get(0).scrollHeight
},
500);
return false;
});
})
}

$('#readmode').click(function () {
$('body').toggleClass('read-mode')
})

function SiderMenu() {
$('#main-container').toggleClass('open');
$('.iconflat').css('width', '50px').css('height', '50px');
$('.openNav').css('height', '50px');
$('#main-container,#mo-nav,.openNav').toggleClass('open')
}

function switchNightMode() {
$('<div class="Cuteen_DarkSky"><div class="Cuteen_DarkPlanet"></div></div>').appendTo($("body")), setTimeout(
function () {
// 判断你的网站当前是否处于暗黑模式
// 下面的代码仅为示例,请按照你的主题文档修改
(YourTheme.dark.mode == "dark")
?
($("html").addClass("DarkMode"),
$('#modeicon').attr("xlink:href", "#icon-sun"))
:
($("html").removeClass("DarkMode"),
$('#modeicon').attr("xlink:href", "#icon-_moon")),
setTimeout(function () {
$(".Cuteen_DarkSky").fadeOut(1e3, function () {
$(this).remove()
})
}, 2e3)
}), 50
}

function checkNightMode() {
if ($("html").hasClass("n-f")) {
$("html").removeClass("day");
$("html").addClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-sun")
return;
}
if ($("html").hasClass("d-f")) {
$("html").removeClass("DarkMode");
$("html").addClass("day");
$('#modeicon').attr("xlink:href", "#icon-_moon")
return;
}
if (volantis.dark.mode == "dark") {
$("html").addClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-sun")
} else {
$("html").removeClass("DarkMode");
$('#modeicon').attr("xlink:href", "#icon-_moon")
}
}
BackTOP();

// 传入暗黑模式触发器的回调函数
// 如果你的主题不支持触发器,则需要你自己实现 switchNightMode() 的执行
// 例如你的主题有"暗黑模式"按钮,则你可以给按钮绑定事件
// 也可以直接修改标签的onclick属性: onclick="switchNightMode()"
// 下面的代码仅为示例,请按照你的主题文档修改
YourTheme.dark.callbacks.push(switchNightMode);

首页动态诗词(通用)

在volantis的配置文件里修改 subtitle 为"<div id="binft"></div>"

1
2
3
4
5
############################### Cover ############################### > start
cover:
...
subtitle: <div id="binft"></div>
...

defer方式引入以下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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
var binft = function (r) {
var isTransparent = true;
function getRandomColor() {
if(isTransparent){
isTransparent = false;
//此处修改字体颜色,最后的 0 和 1 不要改
return "rgba(255,255,255,0)"
}else{
isTransparent = true;
return "rgba(255,255,255,1)"
}
}
function n(r) {
for (var n = document.createDocumentFragment(), i = 0; r > i; i++) {
var oneword = document.createElement("span");
oneword.textContent = "_"; // 此处是末尾字符,如果想用光标样式可以改为"|"
oneword.style.color = getRandomColor();
n.appendChild(oneword);
}
return n
}
function i() {
var t = wordList[c.skillI];
c.step ? c.step-- : (c.step = refreshDelayTime, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : "forward" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = "backward", c.delay = showTotalWordDelayTime) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % wordList.length, c.direction = "forward")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(maxLength, maxLength + c.prefixP) : Math.min(maxLength, t.length - c.skillP))), setTimeout(i, d)
}
var l = "",
//此处改成你自己的诗词
wordList = [
"有花堪折直需折,莫待无花空折枝.",
"闲居少邻并,草径入荒园.鸟宿池边树,僧敲月下门.",
"侯门一入深如海,从此萧郎是路人.",
"才见岭头云似盖,已惊岩下雪如尘.",
"人间万事消磨尽,只有清香似旧时.",
"日暮酒醒人已远,满天风雨下西楼.",
"落灯花,棋未收,叹新丰逆旅淹留.",
"软风吹过窗纱,心期便隔天涯.",
"迷惑失故路,薄暮无宿栖.",
"不见白头相携老,只许与君共天明.",
"晓迎秋露一枝新,不占园中最上春.",
"荷尽已无擎雨盖,菊残犹有傲霜枝.",
"春未绿,鬓先丝.人间别久不成悲.",
"江东子弟多才俊,卷土重来未可知.",
"莫听穿林打叶声,何妨吟啸且徐行.",
"在天愿作比翼鸟,在地愿为连理枝.",
].map(function (r) {
return r + ""
}),
showTotalWordDelayTime = 2,
refreshDelayTime = 1,
maxLength = 1,
d = 75,
c = {
text: "",
prefixP: -maxLength,
skillI: 0,
skillP: 0,
direction: "forward",
delay: showTotalWordDelayTime,
step: refreshDelayTime
};
i()
};
binft(document.getElementById('binft'));
非volantis的使用方法

在需要显示动态诗词的位置(如副标题),将文本替换为"<div id="binft"></div>",然后执行上面的js代码即可

你的主题配置文件里应该会提供副标题的修改

看板娘随音乐启停说话(通用)

如果你仅仅想要获取Aplayer对象并自己实现监听,使用以下代码可以获取第一个metingjs里的Aplayer对象.如果你想要实现看板娘随音乐启停说话,请跳过这句话.

1
let aplayer = document.querySelectorAll("meting-js")[0].aplayer;

新版看板娘已失效,这里直接给出已经修改好的旧版看板娘源文件

引用看板娘js,此处改为你自己的地址,注意 autoload.js 必须在页面加载完毕之后再执行,否则容易报错

1
<script defer src="/live2d-widget/autoload.js"></script>

打开 autoload.js,将 live2d_path 改为你的 widget 文件夹的地址

1
2
3
// 注意:live2d_path 参数应使用绝对路径
//const live2d_path = "https://gcore.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/";
const live2d_path = "/live2d-widget/";

下面的 cdnPath 同理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 加载 waifu.css live2d.min.js waifu-tips.js
if (screen.width >= 768) {
Promise.all([
loadExternalResource(live2d_path + "waifu.css", "css"),
loadExternalResource(live2d_path + "live2d.min.js", "js"),
loadExternalResource(live2d_path + "waifu-tips.js", "js")
]).then(() => {
initWidget({
waifuPath: live2d_path + "waifu-tips.json",
//apiPath: "https://live2d.fghrsh.net/api/",
cdnPath: "/live2d_api/"
});
});
}

我已经在 waifu-tips.js 里定义好了 say() 函数,直接在控制台执行 say("Hello") 即可看到看板娘说话

立即尝试

如果想要实现随音乐启停说话,只需在head里引入或执行下面的js,注意修改最底下的3个数组,换成你自己想要的内容

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
41
42
43
44
45
46
47
48
// 看板娘与APlayer
window.addEventListener('load', function () {
let interval = setInterval(()=>{
let aplayer = document.querySelectorAll("meting-js")[0].aplayer;
if(!aplayer){
return;
}
//播放
aplayer.on("play", ()=>{
music_index = aplayer.list.index;
music_name = aplayer.list.audios[music_index].title;
music_artist = aplayer.list.audios[music_index].artist;
if(say){
say(randomSeek(message_play));
}
});
//暂停
aplayer.on("pause", ()=>{
if(say){
say(randomSeek(message_pause));
}
});
//快进
aplayer.on("seeked", ()=>{
if(say){
say(randomSeek(message_seeked));
}
});
clearInterval(interval)

function randomSeek(list){
return list[Math.floor(Math.random() * list.length)]
.replaceAll("{name}", `<span>${music_name || "未知歌曲"}</span>`)
.replaceAll("{artist}", `<span>${music_artist || "未知作者"}</span>`);
}
}, 300)

let music_index;
let music_name;
let music_artist;

const message_play = [
'开始播放{name}',];
const message_pause = [
'{name}已暂停',];
const message_seeked = [
'快进',];
});
非volantis的使用方法

方法同上

"Hello World"特效(通用)

Hello World

修改主题配置文件里的标题

1
2
3
4
5
############################### Cover ############################### > start
cover:
...
title: '<font><span>Hello</span> <span>World</span></font>'
...

引入以下css样式,推荐添加到 hexo/source/_volantis/style.styl 里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 此处调节字体大小
.top .title font{
font-size: 1em;
}
*/
.top .title span{
transition: 0.5s;
}
.top .title:hover span:nth-child(1){
margin-right: 10px;
}
.top .title:hover span:nth-child(2){
margin-left: 10px;
}
.top .title:hover span{
color: #fff;
text-shadow: 0 0 10px #fff,
0 0 20px #fff,
0 0 40px #fff,
0 0 80px #fff,
0 0 120px #fff,
0 0 160px #fff;
}
非volantis的使用方法

将你的标题修改为

1
<div class="title_example"><font><span>Hello</span> <span>World</span></font></div>

添加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
.title_example{
font-size: 3em;
color: white;
margin: auto;
display: table;
align-items: center;
}
.title_example span{
transition: 0.5s;
}
.title_example:hover span:nth-child(1){
margin-right: 10px;
}
.title_example:hover span:nth-child(2){
margin-left: 10px;
}
.title_example:hover span{
color: #fff;
text-shadow: 0 0 10px #fff,
0 0 20px #fff,
0 0 40px #fff,
0 0 80px #fff,
0 0 120px #fff,
0 0 160px #fff;
}

此外,如果你的主题没有将标题居中,那么你还需要自己实现居中

表格居中

Hexo默认将表格居左

yyxx
2211
3322

添加 css 样式后居中

yyxx
2211
3322

在任意位置引入以下css即可

1
2
3
4
table:not(figure table/* 此处排除代码块 */){
display: table !important;
margin: auto;
}

需要注意的是,你的博客里可能有其他元素(例如Volantis的代码块)也是由表格组成,盲目使用css可能会导致显示异常,需要在not()中排除,否则就会像下面的代码块一样

未排除figure table时的异常代码块

1
2
3
4
int sum=0;
for(int i=0;i<100;i++){
sum += i;
}

KaTeX公式渲染(通用)

KeTeX支持markdown内公式渲染,是常用的网页公式渲染器,以下展示KaTeX渲染效果,示例来自于上一篇文章的例题一

设函数f(x,y)f(x,y)的全微分为df(x,y)=(2ax+by)dx+(2by+ax)dydf(x,y)=(2ax+by)dx+(2by+ax)dy,(aa,bb为常数),且f(0,0)=3,fx(1,1)=3f(0,0)=-3,f_{x}^{'}(1,1)=3,求f(x,y)f(x,y)

本题给的是全微分,但是可以看成两个偏微分,并且较为基础,所以放在第一题

fx=2ax+by,fy=2by+ax\frac{∂f}{∂x}=2ax+by,\frac{∂f}{∂y}=2by+ax

直接对两个偏微分求不定积分,可以得到原函数.注意对x积分时,将y看作常数,因此最后的+C+C实际上应该写作+g(y)+g(y)

f(x,y)=ax2+bxy+g(y)=by2+axy+h(x)f(x,y)=ax^{2}+bxy+g(y)=by^{2}+axy+h(x)

显然两者是同一个函数,因此对应的项的系数也相同,即a=ba=b,对x求偏导,得到fx(1,1)=2a+b=3f_{x}^{'}(1,1)=2a+b=3,故f(x,y)=x2+xy+y23f(x,y)=x^2+xy+y^2-3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% noteblock quote %}

设函数$f(x,y)$的全微分为$df(x,y)=(2ax+by)dx+(2by+ax)dy$,($a$,$b$为常数),且$f(0,0)=-3,f_{x}^{'}(1,1)=3$,求$f(x,y)$

{% endnoteblock %}


本题给的是全微分,但是可以看成两个偏微分,并且较为基础,所以放在第一题

$$
\frac{∂f}{∂x}=2ax+by,\frac{∂f}{∂y}=2by+ax
$$

直接对两个偏微分求不定积分,可以得到原函数.注意对x积分时,将y看作常数,因此最后的$+C$实际上应该写作$+g(y)$

$$
f(x,y)=ax^{2}+bxy+g(y)=by^{2}+axy+h(x)
$$

显然两者是同一个函数,因此对应的项的系数也相同,即$a=b$,对x求偏导,得到$f_{x}^{'}(1,1)=2a+b=3$,故$f(x,y)=x^2+xy+y^2-3$

首先切换渲染器,在hexo目录下控制台执行以下代码(参考自在hexo上使用KaTeX - MicDZ’s blog)

1
2
npm uninstall hexo-renderer-marked --save
npm install hexo-renderer-markdown-it-plus --save

引入以下js与css

1
2
3
<link rel="stylesheet" href="https://unpkg.com/katex@0.16.4/dist/katex.min.css">
<script defer src="https://unpkg.com/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://unpkg.com/katex@0.16.4/dist/contrib/auto-render.min.js"><script>
可选功能

复制公式时替换为KaTeX源码

1
<script src="https://unpkg.com/katex@0.16.4/dist/contrib/copy-tex.min.js"></script>

公式显示不下时添加滑动条

1
2
3
4
.katex-block{
overflow-x: auto;
overflow-y: auto;
}

F=G(Ω(xx0)f(x,y,z)r3dv,Ω(yy0)f(x,y,z)r3dv,Ω(zz0)f(x,y,z)r3dv)\Large\mathop{F}\limits ^{\rightarrow}=G\left(\iiint\limits_Ω\frac{(x-x_0)f(x,y,z)}{r^3}dv,\iiint\limits_Ω\frac{(y-y_0)f(x,y,z)}{r^3}dv,\iiint\limits_Ω\frac{(z-z_0)f(x,y,z)}{r^3}dv\right)

无奖竞猜:上面的公式表示什么物理量?

现在你已经可以在博客与twikoo评论系统中直接插入公式,详细说明请查看KaTeX文档

在下面的输入框输入KaTeX表达式,来快速预览

D(QxPy)dxdy=LPdx+Qdy\Large\iint\limits_D(\frac{\partial Q}{\partial x}-\frac{\partial P}{\partial y})dxdy=\oint_LPdx+Qdy

非volantis的使用方法

方法同上

动态标题(通用)

在用户离开与返回本标签页时修改页面标题,引入以下js

1
2
3
4
5
6
7
8
9
10
11
12
13
var OriginTitle = document.title;
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
document.title = '╭(°A°`)╮ 你去哪了? 快回来!!!';
}else {
document.title = '(ฅ>ω<*ฅ) 你终于回来了 ~';
setTimeout(function () {
if(!document.hidden){
document.title = OriginTitle;
}
}, 2000);
}
});
非volantis的使用方法

方法同上

段落缩进(本站未使用)

_volantis/style.styl里添加以下css

1
2
3
4
5
6
7
8
9
10
11
12
13
// 标题居左,开启后会使主页居左
.post.post-v3.white-box{
text-align: left
text-indent: 2em
}


// 文章段落开头空两格
.md{
text-align: left
text-indent: 2em
}

网页压缩(通用)

在网址前面加上view-source:即可看到本页面压缩效果

在hexo目录下打开控制台,输入

1
2
3
npm install html-minifier
npm install uglify-js
npm install clean-css

在当前目录下创建 minify.js,输入以下代码,其中config可自行修改

展开以显示代码
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
const minify = require("html-minifier").minify;
const uglify = require("uglify-js").minify;
const cleancss = require("clean-css");
const fs = require("fs");
const path = require("path");

const config = {
root: "public", // 压缩目录,也可以修改成其他目录
encoding: "utf-8", // 编码格式
Html: true, // 是否压缩Html
Js: true, // 是否压缩Js
Css: true, // 是否压缩Css
JsInHtml: true, // 是否压缩Html内嵌Js
CssInHtml: true, // 是否压缩Html内嵌Css
showSuccess: true, // 是否打印压缩成功的文件
showError: true, // 是否打印压缩失败的文件
showDetail: false // 遇到异常是否显示详情
}

let total = 0;
let success = 0;

LoadDir(config.root);
console.log(`共压缩${total}个文件,其中${success}个压缩成功`);
function LoadDir(dir){
fs.readdirSync(dir).forEach((filename) => {
const filePath = path.join(dir, filename);
if(fs.statSync(filePath).isFile()){
if(filePath.endsWith(".html") && config.Html){
total++;
CompressHTML(filePath);
}else if(filePath.endsWith(".js") && config.Js){
total++;
CompressJS(filePath);
}else if(filePath.endsWith(".css") && config.Css){
total++;
CompressCSS(filePath);
}
}else{
LoadDir(filePath);
}
});
}


function CompressHTML(file){
try{
const data = fs.readFileSync(file, config.encoding);
const result = minify(data, {
removeComments: true, // 移除注释
collapseWhitespace: true, // 移除空格
minifyJS: config.JsInHtml, // 压缩Html内的Js代码
minifyCSS: config.CssInHtml // 压缩Html内的Css样式
});
if(data !== result){
fs.writeFileSync(file,result, {encoding: config.encoding});
}
if(config.showSuccess){
console.log(`压缩率:${(result.length * 100 / data.length).toFixed(2)}%,文件:${file}`);
}
success++;
}catch (e){
if(config.showError){
console.error(`文件(${file})压缩失败`);
}
if(config.showDetail){
console.error(e);
}
}
}

function CompressJS(file){
try{
const data = fs.readFileSync(file, config.encoding);
const result = uglify(data).code;
if(data !== result){
fs.writeFileSync(file,result, {encoding: config.encoding});
}
if(config.showSuccess){
console.log(`压缩率:${(result.length * 100 / data.length).toFixed(2)}%,文件:${file}`);
}
success++;
}catch (e){
if(config.showError){
console.error(`文件(${file})压缩失败`);
}
if(config.showDetail){
console.error(e);
}
}
}

function CompressCSS(file){
try{
const data = fs.readFileSync(file, config.encoding);
const result = new cleancss({returnPromise: false}).minify(data).styles;
if(data !== result){
fs.writeFileSync(file,result, {encoding: config.encoding});
}
if(config.showSuccess){
console.log(`压缩率:${(result.length * 100 / data.length).toFixed(2)}%,文件:${file}`);
}
success++;
}catch (e){
if(config.showError){
console.error(`文件(${file})压缩失败`);
}
if(config.showDetail){
console.error(e);
}
}
}

先使用 hexo g 生成静态文件,然后控制台输入以下指令压缩网页

1
node minify

压缩范围包括html,js,css,以及html内嵌的css,js,如果出现语法错误会压缩失败

非volantis的使用方法

方法同上

今日诗词改成指定的文字

修改或覆盖hexo/themes/volantis/layout/_widget/blogger.ejs

先搜索找到item.jinrishici的位置,在下方添加item.customword,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<% if (item.title || item.subtitle || item.jinrishici) { %>
<div class='text'>
<% if (item.title){ %>
<h2><%- item.title %></h2>
<% } %>
<% if (item.subtitle){ %>
<%- markdown(item.subtitle) %>
<% } %>
<% if (item.jinrishici){ %>
<p><span id="jinrishici-sentence"><%- item.jinrishici != true ? item.jinrishici : config.title %></span></p>
<script src="https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset="utf-8"></script>
<% } %>

<% if (item.customword){ %>
<p><%- item.customword %></p>
<% } %>

</div>
<% } %>

修改hexo/_config.volantis.yml,找到sidebar下的blogger,添加customword

1
2
3
4
5
6
7
8
9
10
11
12
############################### Sidebar ############################### > start
sidebar:
...
# 侧边栏组件库
widget_library:
# ---------------------------------------
# blogger info widget
blogger:
...
jinrishici: false # Poetry Today. You can set a string, and it will be displayed when loading fails.
customword: 道可道,非常道;名可名,非常名.
...

透明卡片与背景亮度

hexo/source/_volantis/style.styl里添加以下css

1
2
3
4
5
6
7
8
9
10
11
12
13
// 添加透明度
#l_main .post,
#l_side .widget
opacity: 0.8

// 文章不透明,否则会影响阅读
#post.post,
#docs.post
opacity: 1

//背景亮度
.parallax-mirror
filter: brightness(0.5)

Aplayer采用本地音源(通用)

metingjs的官方api不稳定,因此建议改用本地音源来确保稳定播放.该方法在不影响Volantis原有功能的情况下,通过修改metingjs来实现加载本地音乐的功能

修改或覆盖hexo/themes/volantis/layout/_plugins/aplayer/layout.ejs

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<%
let aplayer = theme.plugins.aplayer;
%>
<% if (theme.plugins.aplayer.enable == true) { %>
<% if (aplayer.localMusic.enable == true) { // 采用本地音乐 %>
<%
let audio = aplayer.localMusic.audio; // 获取本地歌曲数组
let template = aplayer.localMusic.template; // 获取模板
// 使用模板补充缺少的信息
audio.forEach((item) => {
let name = item.name;
if(!item.artist) item.artist = template.artist.replaceAll("{name}", name);
if(!item.title) item.title = template.title.replaceAll("{name}", name);
if(!item.url) item.url = template.root + template.url.replaceAll("{name}", name);
if(!item.lrc) item.lrc = template.root + template.lrc.replaceAll("{name}", name);
if(!item.cover) item.cover = template.root + template.cover.replaceAll("{name}", name);
});
%>
<meting-js
theme='<%- aplayer.theme %>'
autoplay='<%- aplayer.autoplay %>'
volume='<%- aplayer.volume %>'
loop='<%- aplayer.loop %>'
order='<%- aplayer.order %>'
fixed='<%- aplayer.fixed %>'
list-max-height='<%- aplayer.list_max_height %>'
server='<%- aplayer.server %>'
type='<%- aplayer.type %>'
id='<%- aplayer.id %>'
list-folded='<%- aplayer.list_folded %>'
audio= <%- encodeURI(JSON.stringify(audio)) // 采用URI编码,防止特殊字符造成的异常 %>
>
</meting-js>
<% } else { %>
<% if (post && post.music) { %>
<meting-js
mini='true'
volume='<%- post.music.volume || aplayer.volume %>'
loop='<%- post.music.loop || aplayer.loop %>'
order='<%- post.music.order || aplayer.order %>'
server='<%- post.music.server || aplayer.server %>'
type='<%- post.music.type || aplayer.type %>'
id='<%- post.music.id || aplayer.id %>'>
</meting-js>
<% } else { %>
<meting-js
theme='<%- aplayer.theme %>'
autoplay='<%- aplayer.autoplay %>'
volume='<%- aplayer.volume %>'
loop='<%- aplayer.loop %>'
order='<%- aplayer.order %>'
fixed='<%- aplayer.fixed %>'
list-max-height='<%- aplayer.list_max_height %>'
server='<%- aplayer.server %>'
type='<%- aplayer.type %>'
id='<%- aplayer.id %>'
list-folded='<%- aplayer.list_folded %>'>
</meting-js>
<% } %>
<% } %>
<% } %>

创建meting-plus.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class MetingJSElement extends HTMLElement {

connectedCallback() {
if (window.APlayer && window.fetch) {
this._init()
this._parse()
}
}

disconnectedCallback() {
if (!this.lock) {
this.aplayer.destroy()
}
}

_camelize(str) {
return str
.replace(/^[_.\- ]+/, '')
.toLowerCase()
.replace(/[_.\- ]+(\w|$)/g, (m, p1) => p1.toUpperCase())
}

_init() {
let config = {}
for (let i = 0; i < this.attributes.length; i += 1) {
config[this._camelize(this.attributes[i].name)] = this.attributes[i].value
}
let keys = [
'server', 'type', 'id', 'api', 'auth',
'auto', 'lock',
'name', 'title', 'artist', 'author', 'url', 'cover', 'pic', 'lyric', 'lrc',
'audio' // 将audio添加到配置信息中
]
this.meta = {}
for (let key of keys) {
this.meta[key] = config[key]
delete config[key]
}
this.config = config

this.api = this.meta.api || window.meting_api || 'https://api.i-meto.com/meting/api?server=:server&type=:type&id=:id&r=:r'
if (this.meta.auto) this._parse_link()
}

_parse_link() {
let rules = [
['music.163.com.*song.*id=(\\d+)', 'netease', 'song'],
['music.163.com.*album.*id=(\\d+)', 'netease', 'album'],
['music.163.com.*artist.*id=(\\d+)', 'netease', 'artist'],
['music.163.com.*playlist.*id=(\\d+)', 'netease', 'playlist'],
['music.163.com.*discover/toplist.*id=(\\d+)', 'netease', 'playlist'],
['y.qq.com.*song/(\\w+).html', 'tencent', 'song'],
['y.qq.com.*album/(\\w+).html', 'tencent', 'album'],
['y.qq.com.*singer/(\\w+).html', 'tencent', 'artist'],
['y.qq.com.*playsquare/(\\w+).html', 'tencent', 'playlist'],
['y.qq.com.*playlist/(\\w+).html', 'tencent', 'playlist'],
['xiami.com.*song/(\\w+)', 'xiami', 'song'],
['xiami.com.*album/(\\w+)', 'xiami', 'album'],
['xiami.com.*artist/(\\w+)', 'xiami', 'artist'],
['xiami.com.*collect/(\\w+)', 'xiami', 'playlist'],
]

for (let rule of rules) {
let patt = new RegExp(rule[0])
let res = patt.exec(this.meta.auto)
if (res !== null) {
this.meta.server = rule[1]
this.meta.type = rule[2]
this.meta.id = res[1]
return
}
}
}

_parse() {
//加载本地歌曲列表
if(this.meta.audio){
this._loadPlayer(JSON.parse(decodeURI(this.meta.audio)))
return
}

if (this.meta.url) {
let result = {
name: this.meta.name || this.meta.title || 'Audio name',
artist: this.meta.artist || this.meta.author || 'Audio artist',
url: this.meta.url,
cover: this.meta.cover || this.meta.pic,
lrc: this.meta.lrc || this.meta.lyric || '',
type: this.meta.type || 'auto',
}
if (!result.lrc) {
this.meta.lrcType = 0
}
if (this.innerText) {
result.lrc = this.innerText
this.meta.lrcType = 2
}
this._loadPlayer([result])
return
}

let url = this.api
.replace(':server', this.meta.server)
.replace(':type', this.meta.type)
.replace(':id', this.meta.id)
.replace(':auth', this.meta.auth)
.replace(':r', Math.random())

fetch(url)
.then(res => res.json())
.then(result => this._loadPlayer(result))
}

_loadPlayer(data) {

let defaultOption = {
audio: data,
mutex: true,
lrcType: this.meta.lrcType || 3,
storageName: 'metingjs'
}

if (!data.length) return

let options = {
...defaultOption,
...this.config,
}
for (let optkey in options) {
if (options[optkey] === 'true' || options[optkey] === 'false') {
options[optkey] = (options[optkey] === 'true')
}
}

let div = document.createElement('div')
options.container = div
this.appendChild(div)

this.aplayer = new APlayer(options)
}

}

if (window.customElements && !window.customElements.get('meting-js')) {
window.MetingJSElement = MetingJSElement
window.customElements.define('meting-js', MetingJSElement)
}

修改配置文件hexo/_config.volantis.yml,将js.meting修改成你刚刚创建的meting-plus.js的路径,并添加localMusic,如下所示

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
############################### Plugins ############################### > start
plugins:
################ required plugins ################
...
# APlayer is only available in mainland China.
# APlayer config: https://github.com/metowolf/MetingJS
aplayer:
enable: true
js:
aplayer: https://unpkg.com/aplayer@1.10/dist/APlayer.min.js
meting: /meting-plus.js # 改成你创建的 meting-plus.js 路径
css: https://unpkg.com/aplayer@1.10/dist/APlayer.min.css
...
# 添加以下配置信息
localMusic:
enable: true # 是否使用本地音源
template: # name是必须的
root: '/music/'
title: '{name}'
artist: '{name}'
url: '{name}.mp3'
lrc: '{name}.lrc'
cover: '{name}.jpg'
audio:
- name: 'Flower Dance' # 歌曲名,会显示在Aplayer中
title: 'Flower Dance' # 标题,Volantis自带的播放提示会以此为准,基本上设置成跟name一样就好
artist: 'Oturans - DJ Okawari' # 作者
url: '/music/Flower Dance.mp3' # 音乐路径
lrc: '/music/Flower Dance.lrc' # 歌词文件路径
cover: '/music/Flower Dance.jpg' # 封面图片路径
- name: '夜的钢琴曲'
artist: 'K. Williams'
...

localMusic.enable决定了是否启用本地音源,localMusic.audio是本地音乐列表,含义如下

参数含义
name歌曲名,会显示在Aplayer中
title标题,Volantis自带的播放提示会以此为准,基本上设置成跟name一样就好
artist作者
url音乐路径
lrc歌词文件路径
cover封面图片路径

考虑到这里可能会出现大量重复操作,故提供了localMusic.template模板功能,在填写localMusic.audio时,你至少要填写name属性,缺失的属性会从localMusic.template里获取,其中字符串里的{name}会被替换为localMusic.audio.name,localMusic.template.root是前缀路径,会被添加在url,lrc,cover前面

例如你设置了

1
2
3
4
5
6
7
8
9
10
11
12
localMusic:
enable: true # 是否使用本地音源
template: # name是必须的
root: 'https://example.com/music/'
title: '{name}'
artist: 'This is {name}'
url: '{name}.mp3'
lrc: 'all.lrc'
cover: 'https://example.com/music/{name}.jpg'
audio:
- name: 'Flower Dance'
title: '花之舞'

则使用模板替换后的实际audio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
audio:
- name: 'Flower Dance' # name是必须的

# 因为 title 已经给出了,所以模板不生效
title: '花之舞'

# {name} 被替换成 Flower Dance,前面的 'This is '不变
artist: 'This is Flower Dance'

# url 前面会加上前缀路径,同时 {name} 被替换
url: 'https://example.com/music/Flower Dance.mp3'

# lrc 前面加上了前缀路径,但是没有 {name},所以后面不变
# 这意味着如果你没有给出lrc的值,那么所有歌曲都会加载同一个歌词文件
# 如果是纯音乐的话,可以这么做
lrc: 'https://example.com/music/all.lrc'

# {name} 被替换成 Flower Dance
# 但是由于 cover 里写上了完整路径,而 root 又被加到前面,所以导致同样的路径出现了两次
# 如果你希望不同文件分开放,请把 root 设置为 '',并在 url,lrc,cover 里写上完整路径
cover: 'https://example.com/music/https://example.com/music/Flower Dance.jpg'

需要自定义功能的可以自行修改hexo/themes/volantis/layout/_plugins/aplayer/layout.ejs

非volantis的使用方法

如果你的主题也是使用metingjs来生成Aplayer,那么你需要先把metingjs改成上面的meting-plus,然后根据你的配置文件规则,修改上面的ejs文件,具体修改哪个文件根据你的主题而定,其中重点修改部分在于下面的代码

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
<% if (aplayer.localMusic.enable == true) { // 采用本地音乐 %>
<%
let audio = aplayer.localMusic.audio; // 获取本地歌曲数组
let template = aplayer.localMusic.template; // 获取模板
// 使用模板补充缺少的信息
audio.forEach((item) => {
let name = item.name;
if(!item.artist) item.artist = template.artist.replaceAll("{name}", name);
if(!item.title) item.title = template.title.replaceAll("{name}", name);
if(!item.url) item.url = template.root + template.url.replaceAll("{name}", name);
if(!item.lrc) item.lrc = template.root + template.lrc.replaceAll("{name}", name);
if(!item.cover) item.cover = template.root + template.cover.replaceAll("{name}", name);
});
%>
<meting-js
theme='<%- aplayer.theme %>'
autoplay='<%- aplayer.autoplay %>'
volume='<%- aplayer.volume %>'
loop='<%- aplayer.loop %>'
order='<%- aplayer.order %>'
fixed='<%- aplayer.fixed %>'
list-max-height='<%- aplayer.list_max_height %>'
server='<%- aplayer.server %>'
type='<%- aplayer.type %>'
id='<%- aplayer.id %>'
list-folded='<%- aplayer.list_folded %>'
audio= <%- encodeURI(JSON.stringify(audio)) // 采用URI编码,防止特殊字符造成的异常
>
</meting-js>
<% } else { %>
<% } %>

多背景图优化

如果你的背景图按照某一规律命名(例如数字.jpg),且数量很多,则可以修改主题代码,每次随机生成.但如果你是上传到图床,且命名都是随机的,那么就老老实实去配置文件hexo\_config.volantis.yml里写

假设你把背景图放在https://cdn.example.com/img/目录下,且图片命名是1.jpg100.jpg,那么你可以按照以下示例修改代码

修改或覆盖hexo/themes/volantis/layout/_plugins/parallax/script.ejs

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<script>
const img_config = {
min: 1,
max: 100,
root: "https://cdn.example.com/img/"
}
let IntervalParallax = null;

function parallax() {
<% if (theme.plugins.parallax.position == "fixed") { %>
let ParallaxWindow = document.querySelector("html");
<% } else{ %>
let ParallaxWindow = document.querySelector("#parallax-window");
<% } %>
Parallax.window = ParallaxWindow;
Parallax.options.fade = <%- theme.plugins.parallax.fade %>;
Parallax.cache = 1;
next_parallax();
Parallax.init();
IntervalParallax = setInterval(function () {
if(!document.hidden){
next_parallax();
}
}, '<%- theme.plugins.parallax.duration %>');
}

function next_parallax() {
if (typeof Parallax == "undefined") {
return
}
<% if (theme.plugins.parallax.position != "fixed") { %>
if (!document.querySelector("#full") && !document.querySelector("#half")) {
return
}
<% } %>
// 生成随机图片地址
let index = Math.floor(Math.random() * (img_config.max - img_config.min + 1) + img_config.min);
let img = `${img_config.root}${index}.jpg`;
Parallax.options.src = img;
Parallax.start();
}

var runningOnBrowser = typeof window !== "undefined";
var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent);
if (!isBot) {
volantis.js('<%- theme.cdn.map.js.parallax %>').then(() => {
parallax()
})
volantis.pjax.send(() => {
clearInterval(IntervalParallax)
}, "clearIntervalParallax");
volantis.pjax.push(parallax);
}
</script>

评论