使用 hexo 博客的人有很多,但是大部分人的最终的效果几乎都不一样,因为他们都或多或少对主题底层进行了修改,如果你也想根据现在使用的主题进行修改,那么这篇文章应该帮助你。

适用人群

  1. 博客使用 hexo 搭建
  2. 渲染器用的是 pug 和 ejs 和 stylus

判断渲染器只需要看你的博客根目录下的 package.json 文件,如果有 hexo-renderer-pug 和 hexo-renderer-stylus 这样的依赖,那么就适用

明确思路

下手之前,我们得先明白,一个网页,是由三部分组成,分别是:

  1. HTML:网页的结构,也就是网页的骨架
  2. CSS:网页的样式,也就是网页的皮肤
  3. JavaScript:网页的动态效果,也就是网页的功能

只要明白了这个,我们就很好下手了,首先我们从网页结构开始下手

研究界面渲染结构

我们进入到主题的目录,目录的结构如下,我这里以我使用的 solitude 主题为例

我们随便进入一个 layout 目录下的一个 pug 文件,这里以 includes 目录下的 layout.pug 文件为例

我们可以看到以下内容

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
- page.type = is_home() ? 'solitude' : page.type

doctype html
html(lang=config.language, data-theme="light")
head
include ./head.pug
body#body(data-type=page.type)
// universe
if theme.display_mode.universe
canvas#universe

// loading
if theme.loading.fullpage
include ./loading.pug

// console
include ./console.pug

// sidebar
include ./sidebar.pug

// keyboard
if theme.keyboard.enable
include ./keyboard.pug

#body-wrap(class = is_post() ? 'post' : 'page')
include ./header.pug

if(page.type !== '404')
block content

footer#footer
include ./footer.pug
else
.error#body-wrap
block content

// right_menu
if theme.right_menu.enable
include rightmenu

// inject body
include ./inject/body.pug

// search
include ./widgets/third-party/search/index.pug

// Tianli-Talk
if theme.tianli_talk.enable
include ./widgets/third-party/tianli-talk.pug

// music
if theme.capsule.enable
include ./widgets/third-party/music.pug

如果你看不懂 pug 语法也没关系,只要你会一点点基本的前端知识,现学两分钟就可以学会 pug 的基本使用

这里也给出 pug 语法文档

通过观察上述的 pug 文件的内容我们不难发现,pug 文件中所有标签的用法和 html 一样,你想用什么标签就直接写标签名字就好,而不用像 html 一样使用一对闭合标签

pug 文件中以缩进来表达标签父子关系和编程常用的{},比如你想在一个 div 里面再添加一个 ul 标签,就可以这样

1
2
div
ul

想给标签添加类名、id 或者属性可以分别使用 . # (),想添加文本直接在标签后面先添加一个空格然后再写就好,例如下面代码描述的是一个 id 为 nav 的 ul 标签,包含一个类名为 nav-item 的 li 标签,li 标签中又包含了一个类名为 nav-link 的 a 标签,其中 a 标签有一个 href 属性值为 #,同时 a 标签的文本内容为首页

1
2
3
ul#nav
li.nav-item
a.nav-link(href="#") 首页

接着我们看结构控制语法,首先是 if 判断语句

只需要一个 if 语句,然后接一个空格,空格后接条件就好,接着需要一个缩进来判断成功后的内容,例如下面语句表达的是如果 theme.display_mode.universe 为 true,则渲染一个 canvas 标签,id 为 universe

1
2
3
4
if <判断条件>
<判断成功后执行的语句>
if theme.display_mode.universe
canvas#universe

each 循环语句

1
2
3
4
each <变量名> in <数组名>
<循环体>
each item in list
li= item.name //因为文本使用的是变量,所以需要在标签后用=号,而不能直接在空格后面写文本

include 语句,后面跟随一个 pug 文件的路径,就等于把那个 pug 文件的内容复制到这里

1
2
include <文件路径>
include ./head.pug

修改界面结构

比如新增一个右键菜单功能,你可以新建一个 rightmenu.pug 文件,然后像下面这样编写内容,然后在需要使用右键菜单的界面使用 include 语句引入一下就好啦

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
- let custom_list = theme.right_menu.custom_list || []

div#rightMenu
div.rightMenu-group.rightMenu-small
div.rightMenu-item#menu-backward
i.solitude.st-arrow-left-line
div.rightMenu-item#menu-forward
i.solitude.st-arrow-right-line
div.rightMenu-item#menu-refresh
i.solitude.st-restart-line
div.rightMenu-item#menu-top
i.solitude.st-arrow-up-line
div.rightMenu-group.rightMenu-line.rightMenuPlugin
div.rightMenu-item#menu-copytext
i.solitude.st-copy-fill
span= _p('right_menu.copy')
div.rightMenu-item#menu-pastetext
i.solitude.st-clipboard-fill
span= _p('right_menu.paste')
if theme.comment.use
div.rightMenu-item#menu-commenttext
i.solitude.st-chat-new-fill
span= _p('right_menu.comment')
div.rightMenu-item#menu-newwindow
i.solitude.st-window-fill
span= _p('right_menu.new')
div.rightMenu-item#menu-copylink
i.solitude.st-link-line
span= _p('right_menu.link')
div.rightMenu-item#menu-copyimg
i.solitude.st-copy-fill
span= _p('right_menu.img')
div.rightMenu-item#menu-downloadimg
i.solitude.st-download-cloud-fill
span= _p('right_menu.downloadImg')
if theme.search.enable
div.rightMenu-item#menu-search
i.solitude.st-search-line
span= _p('right_menu.search')
if theme.capsule.enable
div.rightMenu-item#menu-music-toggle
i.solitude.st-play-fill
span= _p('right_menu.music.start')
div.rightMenu-item#menu-music-back
i.solitude.st-skip-back-fill
span= _p('right_menu.music.back')
div.rightMenu-item#menu-music-forward
i.solitude.st-skip-forward-fill
span= _p('right_menu.music.forward')
div.rightMenu-item#menu-music-copyMusicName
i.solitude.st-copy-fill
span= _p('right_menu.music.copyMusicName')
div.rightMenu-group.rightMenu-line.rightMenuOther
each item,index in custom_list
div.rightMenu-item(id=item.id class=item.class onclick=item.click+'||rm.hideRightMenu()')
i.solitude(class=item.icon)
span= item.name
div.rightMenu-group.rightMenu-line.rightMenuOther
if theme.right_menu.commentBarrage && theme.comment.use && theme.comment.commentBarrage
div.rightMenu-item#menu-commentBarrage(onclick="sco.switchCommentBarrage()")
i.solitude.st-chat-fill
span.menu-commentBarrage-text= _p('right_menu.barrage.close')
div.rightMenu-item#menu-darkmode(onclick="sco.switchDarkMode()")
i.solitude.st-moon-clear-fill
case theme.display_mode.type
when 'auto'
when 'light'
span.menu-darkmode-text= _p('right_menu.light')
when 'dark'
span.menu-darkmode-text= _p('right_menu.dark')
if theme.right_menu.translate.enable
div.rightMenu-item#menu-translate
case theme.right_menu.translate.defaultEncoding
when 2
i.solitude.st-panben-line
span= _p('right_menu.chs_to_cht')
when 1
i.solitude.st-jianben-line
span= _p('right_menu.cht_to_chs')
div#rightmenu-mask

如果你想修改现在的结构,但是找不到该修改哪个文件怎么办?这个很简单,浏览器找到你想修改的界面,按 F12 打开开发者工具,然后找到只有在这个界面才会出现的元素,根据类名、结构、id 等等信息跟 pug 文件进行对比,就知道控制当前界面结构的 pug 文件是哪个啦

修改样式

其实只要会修改结构修改样式就很简单了,在结构中控制标签的类名、id、属性,然后再另外写一个 css 文件,在 pug 文件里面使用 link 标签引入你的 css 文件就好啦,比如

1
2
head
link(rel="stylesheet" href="./style.css")

这样是比较方便的,就是你这个界面的样式不会污染到全局,只会作用与当前界面,推荐这样使用的

你也可以直接在结构中使用 style 标签编写样式,类似于这样,这个也是比较推荐使用的,缺点就是不方便管理和修改

1
2
3
4
style.
#app {
background-color: #f0f0f0;
}

如果你想引入一个所有界面的样式,直接在主题中的 inject 配置项或者 extends 配置项的 head 或者 body 下添加就好,比如

添加 js

要添加交互的,可以直接在 pug 文件中使用 script 标签,有两种使用方式

第一种是直接在 script 标签中使用 js 代码,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
script.
fetch("#{tj.url}")
.then(res => res.text())
.then(data => {
const title = ["最近活跃", "今日人数", "今日访问", "昨日人数", "昨日访问", "本月访问", "总访问量"];
let num = data.match(/(<\/span><span>).*?(\/span><\/p>)/g);
num = num.map(el => {
let val = el.replace(/(<\/span><span>)/g, "");
return val.replace(/(<\/span><\/p>)/g, "");
});
const s = document.getElementById("statistic");
let html = '';
for (let i = 0; i < num.length; i++) {
if (i === 0 || i === num.length - 1) continue;
html += `<div><span>${title[i]}</span><span id="${title[i]}">${num[i]}</span></div>`;
}
s.innerHTML = html;
});

也可以编写一个 js 文件,然后使用 script 标签的 src 属性,值为 js 文件的地址

1
script(src="https://test.com/script.js")