编写自己的hexo主题

hexo

前言

开始使用 Hexo 时,使用的都是其他大佬做好的主题,时间久了就产生了不如意的地方,要么是功能太过繁杂自己用不到,要么就是自己想要的东西主题上没有,于是乎,我有了一个自己写主题的想法,便开始接触学习前端,到现在,我的 Hexo 博客网站,用的都是自己写的主题。[https://blog.ninglans.top]

在我制作主题的过程中,参考了很多大佬写的主题,例如:NexTyiliaicarus 等等,在这里表示感谢。

在正式开始编写自己的主题之前,希望你多少具备点以下知识:

  • 前端知识:包含 HTML、CSS、JavaScript(这是一切的前提,不用多熟练,可以一边写一边练)
  • 一点基础的编程语法(至少知道什么是 forif
  • 会查看资料,包含但不限于官方文档、第三方文档
  • 出现问题会主动去寻找解决方法,推荐 Google
  • 模板引擎和预处理(这两个我是一边写一边学的)

提醒

编写本文我不想重复造太多轮子,大多属于小坑提醒之类的。

#预备知识

#模板引擎

Hexo 支持主流的模板引擎,如: EJSJadeSwing 等,本文我使用的是 EJS,可查看 文档

Hexo 的页面都是通过模板引擎渲染出来的页面,它可以实现一些代码的复用,若是采用一般的 HTML,则会造成很大的工作量,非常不方便,因此需要事先了解和学习一下。

#CSS 预处理

与模板引擎一样,CSS 预处理能够更方便地写 CSS 样式,Hexo 中也支持大量的预处理,如: sassStylusLess 等,本文我是用的是 Stylus,可查看 文档

#Hexo 官方文档

可到 Hexo 查看其文档,里面有很多用模板引擎用得到的变量以及辅助函数。

#主题结构

Hexo 主题结构通常如下所示:

1
2
3
4
5
theme folder
├── languages // 多语言文件夹
├── layout // 页面文件
├── source // 资源文件
└── _config.yml // 主题配置文件

languages 文件夹放有一个或多个语言文件。

layout 文件夹下面用于存放页面文件,通常第一层有 Index 首页Archive 归档页Tag 标签页Category 分类页Post 文章页Page 页面详情页layout 布局 ,一般还会创建一个公共页面的文件夹,该文件夹用于放置一个页面的部分内容,用于复用。

source 文件夹用于放一些资源文件,例如: 字体CSSJS图片 等,也会根据他们的文件类型进行再次分类,图片放到图片的文件夹,JS 放到 JS 的文件夹。

以上内容可以手动自建,也可以使用 generator-hexo-theme 来生成主题结构。

#注意事项

我们在编写主题的时候,都是一边写一边就要看到效果,所以需要使用 hexo s 命令在本地运行,更改文件刷新页面就能看到效果,嫌弃麻烦的可使用 hexo-browsersync 插件,当修改主题文件保存后,浏览器将自动进行刷新。

在使用 hexo s 之前要先 hexo g修改主题配置文件时,不需要重新构建文件,修改根目录配置文件时,需要重新构建( hexo g

#页面编写

编写推荐使用 VS Code 编辑器并安装 ejs、Stylus 插件。

先从 layout.ejs 文件开始,该文件是布局文件,其他页面都按照其来进行渲染,编写时遵循 HTML5 规范

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="<%= config.language %>">
<head>
<%- partial('common/head') %>
</head>
<body>
<%- partial('common/header') %>
<div class="wrapper">
<%- body %>
</div>
<%- partial('common/footer') %>
</body>
</html>

参考 官方文档 - 模板

  • config.language 表示使用根目录配置文件中 language 属性,假设配置文件中该属性填的是 zh-CN ,则最终渲染成 `` 。
  • partial() 用于引入公共布局,当引用后,每个页面都会存在你引用的这个布局,上面一共引用了三个文件 headheaderfooter ,三个文件都在 common 文件夹下,这时候应该建立该文件夹,并在下面创建对应的三个 ejs 文件。假设 head.ejs 中的内容为 this is head ,最终渲染成如下:(每个页面都会存在此内容)
1
2
3
<head>
this is head
</head>
  • <%- body %> 表示其他页面内容,例如: index.ejsarchive.ejs 等,假设 index.ejs 内容为 this is index ,则最终渲染成如下:(因为是写在首页文件中,所以只有首页会存在该内容)
1
2
3
<div class="wrapper">
this is index
</div>

现在我们把其他文件也假设写出来。

文件内容
index.ejsindex
head.ejshead
header.ejsheader
footer.ejsfooter

最终渲染成的 index 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="zh-CN">
<head>
head
</head>
<body>
header
<div class="wrapper">
index
</div>
footer
</body>
</html>

其他页面都是一样的用法,都是在生成在 <%- body %> 里面

#数据使用

Hexo 中存在着三种变量数据:

  • 根目录配置文件:上面提到,该文件内的属性采用 config.XXX 来使用,更改此处属性要重新构建才能生效
  • 主题配置文件:和根目录配置文件使用方法一样,不过需要使用 theme.XXX 来调用,更改后无需重新构建
  • Hexo 预置:可查看 官方文档

#配置文件数据

1
2
3
4
5
6
7
8
# 根目录配置文件
title: STAUNCHKAI
subtitle: this is a subtitle

# 主题配置文件
avatar: /images/avatar.png
a
b: https://staunchkai.com

分类页面 category.ejs 中内容如下:

1
2
3
4
5
6
7
8
<div class="category-page">
<h1><%= config.title %></h1>
<p><%= config.subtitle %></p>
</div>
<div class="cateogry-page-other">
<img src="<%= theme.avatar %>">
<p><%= theme.a.b %></p>
</div>

最终渲染结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="zh-CN">
<head>
head
</head>
<body>
header
<div class="wrapper">
<div class="category-page">
<h1>STAUNCHKAI</h1>
<p>this is a subtitle</p>
</div>
<div class="cateogry-page-other">
<img src="/images/avatar.png">
<p>https://staunchkai.com</p>
</div>
</div>
footer
</body>
</html>

#Hexo 预置数据

post.ejs 文件内容如下:

1
2
3
4
5
6
7
<div class="post">
<h1><%= page.title %></h1>
<p><%= page.tip %></p>
<div class="post-content">
<%- page.content %>
</div>
</div>

一篇 md 文章内容如下:

1
2
3
4
5
---
title: 这是一篇文章
tip: hexo 开发
---
主内容...

文章页面最终渲染成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="zh-CN">
<head>
head
</head>
<body>
header
<div class="wrapper">
<div class="post">
<h1>这是一篇文章</h1>
<p>hexo 开发</p>
<div class="post-content">
主内容...
</div>
</div>
</div>
footer
</body>
</html>

#例子

举个复杂一丢丢的例子,主题配置文件如下:

1
2
3
4
5
6
7
8
9
10
social:
Github:
url: https://github.com/staunchkai
icon: icon-github
Email:
url: mailto:staunchkai@outlook.com
icon: icon-email
Rss:
url: /atom.xml
icon: icon-rss

要把 social 属性全部读取出来,如下:

1
2
3
4
5
6
<div class="social">
<% for (let i in theme.social) { %>
<% let social = theme.socail[i]; %>
<a href="<%- url_for(social.url) %>" class="<%= social.icon %>"><%= i %></a>
<% } %>
</div>

最终结果:

1
2
3
4
5
<div class="social">
<a href="https://github.com/staunchkai" class="icon-github">Github</a>
<a href="mailto:staunchkai@outlook.com" class="icon-email">Email</a>
<a href="/atom.xml" class="icon-rss">Rss</a>
</div>

提醒

按照上面的,导航栏菜单也是同样的方法。

#辅助函数

辅助函数参考 官方文档,官方文档最近几版更新的都比较详细,有具体的例子。在这里我挑几个来举例子。

#条件函数

1
2
3
4
5
6
7
<% if (is_home()) { %>	// 如果是首页
<%- partial('common/header') %> // 引入 header 文件
<% } %>

<% if (!is_archive()) { %> // 如果不是归档页
<%- partial('common/footer') %> // 引入 footer 文件
<% } %>

#日期

官方文档中有多个日期函数,但是我觉得使用下面这一个就足够了。

1
2
3
4
5
6
7
8
9
10
11
12
官方文档
<%- date(date, [format]) %>

// 示例:
<%- date(Date.now()) %>
// 2013-01-01

<%- date(Date.now(), 'YYYY/M/D') %>
// Jan 1 2013

页面中的日期
<%- date(page.date, 'YYYY/M/D') %> // 这样就能按照格式获取到页面中的日期

#列表

官方文档中有 list_categorieslist_posts 等函数,都有具体的使用方法,在这里对于列表我写出我常用的方法。

文章列表:

对于文章通常有两种,一种是每页只显示 config.per_page 数量的文章,带有分页,另外一种是一个页面显示所有文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 带分页,使用 Hexo 预置变量 page.posts
<% page.posts.each(function(post) { %> // 因为这里有个对象 post
<h1><% post.title %></h1> // 所以这里才可以用 post.title
<%- partial('common/post-card') %> // 这时引用一个文件
<% }); %>

post-card 文件内容为:
<div class="post-card">
<%= post.title %> // 这里是会报错的,无法通过
</div>

需要修改引用为:
<%- partial('common/post-card', {post: post}) %> // 把 post 传过去才可使用

// 若没有传过去,只能使用 page.title,因为 Hexo 中没有预置 post 这个东东,具体可查看文档
// 一个页面显示所有文章
<% site.posts.each(function(post) { %>
<h1><% post.title %></h1>
<%- partial('common/post-card', {post: post}) %>
<% }); %>

提醒

上面两个,分别使用了官方文档中的两个变量 site.postspage.posts ,请注意官方文档的重要性。

分类列表:

1
2
3
4
// 文章分类显示
<% page.categories.each(function(category) { %> // 这里就没用 post.categories 了,除非是包含在上面的文章列表循环里面才可以用
<a href="<%- url_for(category.path) %>"><%= category.name %></a>
<% }); %>

标签列表: 根据分类列表估计能把标签列表给弄出来了

#其他页面

主题中原有的 category.ejstag.ejs 文件都是属于单个对象的页面,啥意思呢? 假设一篇文章 Front-matter 如下:

1
2
3
4
5
6
7
8
---
categories:
- 分类1
- 分类2
tags:
- tag1
- tag2
---

category.ejs 页面只显示单个分类,当你点击分类 1 跳转过去的页面就是 category ,它不会显示出网站中所有的分类。

根据上面的可以看出,目前网站存在两个分类,想要全部显示出来,需要自行创建一个页面,这也就是在我们使用其他主题的时候,为啥什么 categoriestags 需要在根目录 source 文件夹下面创一个对应的文件夹,再创建一个 index.md 的原因所在,Hexo 里面没有预置这个东西。

tag.ejs 也同理。

还记得主题文件夹下面的 page.ejs 文件吗?他的作用就是用来创建其他文件的,只要把对应的内容写到这个文件里面就可以了。那我们除了分类、标签页面,还有友情链接、关于页面等等,难道全部写到一个文件里面吗?

显然这种做法是可行的,但是会导致文件内容复杂,将来不好维护,通常都会重新新建对应的一个页面文件,然后再到 page.ejs 里面引用即可。

下面做一个 关于 页面的例子:

  1. 首先在根目录资源文件夹创建一个名为 about 的文件夹,再到该文件夹下创建一个 index.md 文件,内容为:
1
2
3
4
5
---
title: 关于
type: 'about'
---
这是一个关于页面内容
  1. 到主题文件夹中布局文件夹中创建一个 about.ejs 页面,内容为:
1
2
3
4
<div class="about-page">
<h1><%= page.title %></h1>
<p><%- page.content %></p>
</div>
  1. page.ejs 中引入 about.ejs
1
2
3
4
5
6
7
8
<% if (is_page() && page.type === 'about') { %>	// 该文件是其他页面的集合,所以得判断啥情况引入啥文件
<%- partial('about') %>
<% } %>

// 假设引入友情链接文件
<% if (is_page() && page.type === 'links') { %> // is_page() 是啥请看官方文档 - 辅助函数
<%- partial('links') %>
<% } %>
  1. 最终渲染结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="zh-CN">
<head>
head
</head>
<body>
header
<div class="wrapper">
<div class="about-page">
<h1>关于</h1>
<p>这是一个关于页面内容</p>
</div>
</div>
footer
</body>
</html>

categories.ejstags.ejs 页面是不是就可以使用 site.categories.each...site.tags.each... 用同样的方式给弄出来了。

#样式文件

如何在样式代码文件中引用主题文件的参数呢?假设主题配置文件中如下:

1
color: '#c5cae9'

在 ejs 页面中完全可以使用 theme.color 来获取到该属性。

在 Stylus 文件中使用如下:

1
2
3
$main-color = hexo-config('color')
// 或
mainColor = unquote(hexo-config("color"))

#结语

目前就想到这些了,纯小白肯定是没法看懂的,本文大多属于小坑小提醒类型,最好具备文首所述的那些知识再来折腾比较好。

本篇文章转于https://staunchkai.com/hexo_develop.html

本文作者:Konger

本文链接: https://blog.ninglans.top/posts/60197/

评论