# 编写自己的 Hexo 主题 @CareyQ Finish
# 前言
开始使用 Hexo 时,使用的都是其他大佬做好的主题,时间久了就产生了不如意的地方,要么是功能太过繁杂自己用不到,要么就是自己想要的东西主题上没有,于是乎,我有了一个自己写主题的想法,便开始接触学习前端,到现在,我的 Hexo 博客网站,用的都是自己写的主题。https://careyq.cool (opens new window)
这篇文章应该在 18 年就出现的,由于懒癌晚期拖到了现在。
在我制作主题的过程中,参考了很多大佬写的主题,例如:NexT (opens new window)、yilia (opens new window)、icarus (opens new window) 等等,在这里表示感谢。
在正式开始编写自己的主题之前,希望你多少具备点以下知识:
- 前端知识:包含 HTML、CSS、JavaScript(这是一切的前提,不用多熟练,可以一边写一边练)
- 一点基础的编程语法(至少知道什么是
for
、if
) - 会查看资料,包含但不限于官方文档、第三方文档
- 出现问题会主动去寻找解决方法,推荐 Google
- 模板引擎和预处理(这两个我是一边写一边学的)
提醒
编写本文我不想重复造太多轮子,大多属于小坑提醒之类的。
# 预备知识
# 模板引擎
Hexo 支持主流的模板引擎,如: EJS
、 Jade
、 Swing
等,本文我使用的是 EJS,可查看 文档 (opens new window)。
Hexo 的页面都是通过模板引擎渲染出来的页面,它可以实现一些代码的复用,若是采用一般的 HTML,则会造成很大的工作量,非常不方便,因此需要事先了解和学习一下。
# CSS 预处理
与模板引擎一样,CSS 预处理能够更方便地写 CSS 样式,Hexo 中也支持大量的预处理,如: sass
、 Stylus
、 Less
等,本文我是用的是 Stylus,可查看 文档 (opens new window)。
# Hexo 官方文档
可到 Hexo (opens new window) 查看其文档,里面有很多用模板引擎用得到的变量以及辅助函数。
# 主题结构
Hexo 主题结构通常如下所示:
theme folder
├── languages // 多语言文件夹
├── layout // 页面文件
├── source // 资源文件
└── _config.yml // 主题配置文件
languages
文件夹放有一个或多个语言文件。
layout
文件夹下面用于存放页面文件,通常第一层有 Index 首页
、 Archive 归档页
、 Tag 标签页
、 Category 分类页
、 Post 文章页
、 Page 页面详情页
、 layout 布局
,一般还会创建一个公共页面的文件夹,该文件夹用于放置一个页面的部分内容,用于复用。
source
文件夹用于放一些资源文件,例如: 字体
、 CSS
、 JS
、 图片
等,也会根据他们的文件类型进行再次分类,图片放到图片的文件夹,JS 放到 JS 的文件夹。
以上内容可以手动自建,也可以使用 generator-hexo-theme (opens new window) 来生成主题结构。
# 注意事项
我们在编写主题的时候,都是一边写一边就要看到效果,所以需要使用 hexo s
命令在本地运行,更改文件刷新页面就能看到效果,嫌弃麻烦的可使用 hexo-browsersync (opens new window) 插件,当修改主题文件保存后,浏览器将自动进行刷新。
在使用 hexo s
之前要先 hexo g
,修改主题配置文件时,不需要重新构建文件,修改根目录配置文件时,需要重新构建( hexo g
)。
# 页面编写
编写推荐使用 VS Code (opens new window) 编辑器并安装 ejs、Stylus 插件。
先从 layout.ejs
文件开始,该文件是布局文件,其他页面都按照其来进行渲染,编写时遵循 HTML5 规范 (opens new window)。
<!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>
参考 官方文档 - 模板 (opens new window)。
config.language
表示使用根目录配置文件中language
属性,假设配置文件中该属性填的是zh-CN
,则最终渲染成<html lang="zh-CN">
。partial()
用于引入公共布局,当引用后,每个页面都会存在你引用的这个布局,上面一共引用了三个文件head
、header
、footer
,三个文件都在common
文件夹下,这时候应该建立该文件夹,并在下面创建对应的三个 ejs 文件。假设head.ejs
中的内容为this is head
,最终渲染成如下:(每个页面都会存在此内容)
<head>
this is head
</head>
<%- body %>
表示其他页面内容,例如:index.ejs
、archive.ejs
等,假设index.ejs
内容为this is index
,则最终渲染成如下:(因为是写在首页文件中,所以只有首页会存在该内容)
<div class="wrapper">
this is index
</div>
现在我们把其他文件也假设写出来。
文件 | 内容 |
---|---|
index.ejs | index |
head.ejs | head |
header.ejs | header |
footer.ejs | footer |
最终渲染成的 index 为:
<!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 预置:可查看 官方文档 (opens new window)。
# 配置文件数据
# 根目录配置文件
title: STAUNCHKAI
subtitle: this is a subtitle
# 主题配置文件
avatar: /images/avatar.png
a
b: https://staunchkai.com
分类页面 category.ejs
中内容如下:
<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>
最终渲染结果为:
<!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
文件内容如下:
<div class="post">
<h1><%= page.title %></h1>
<p><%= page.tip %></p>
<div class="post-content">
<%- page.content %>
</div>
</div>
一篇 md
文章内容如下:
---
title: 这是一篇文章
tip: hexo 开发
---
主内容...
文章页面最终渲染成:
<!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>
# 例子
举个复杂一丢丢的例子,主题配置文件如下:
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 属性全部读取出来,如下:
<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>
最终结果:
<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>
提醒
按照上面的,导航栏菜单也是同样的方法。
# 辅助函数
辅助函数参考 官方文档 (opens new window),官方文档最近几版更新的都比较详细,有具体的例子。在这里我挑几个来举例子。
# 条件函数
<% if (is_home()) { %> // 如果是首页
<%- partial('common/header') %> // 引入 header 文件
<% } %>
<% if (!is_archive()) { %> // 如果不是归档页
<%- partial('common/footer') %> // 引入 footer 文件
<% } %>
# 日期
官方文档中有多个日期函数,但是我觉得使用下面这一个就足够了。
官方文档
<%- 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_categories
、 list_posts
等函数,都有具体的使用方法,在这里对于列表我写出我常用的方法。
文章列表:
对于文章通常有两种,一种是每页只显示 config.per_page
数量的文章,带有分页,另外一种是一个页面显示所有文章。
// 带分页,使用 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.posts
和 page.posts
,请注意官方文档的重要性。
分类列表:
// 文章分类显示
<% page.categories.each(function(category) { %> // 这里就没用 post.categories 了,除非是包含在上面的文章列表循环里面才可以用
<a href="<%- url_for(category.path) %>"><%= category.name %></a>
<% }); %>
标签列表: 根据分类列表估计能把标签列表给弄出来了
# 其他页面
主题中原有的 category.ejs
和 tag.ejs
文件都是属于单个对象的页面,啥意思呢? 假设一篇文章 Front-matter
如下:
---
categories:
- 分类1
- 分类2
tags:
- tag1
- tag2
---
category.ejs
页面只显示单个分类,当你点击分类 1 跳转过去的页面就是 category
,它不会显示出网站中所有的分类。
根据上面的可以看出,目前网站存在两个分类,想要全部显示出来,需要自行创建一个页面,这也就是在我们使用其他主题的时候,为啥什么 categories
、 tags
需要在根目录 source
文件夹下面创一个对应的文件夹,再创建一个 index.md
的原因所在,Hexo 里面没有预置这个东西。
tag.ejs
也同理。
还记得主题文件夹下面的 page.ejs
文件吗?他的作用就是用来创建其他文件的,只要把对应的内容写到这个文件里面就可以了。那我们除了分类、标签页面,还有友情链接、关于页面等等,难道全部写到一个文件里面吗?
显然这种做法是可行的,但是会导致文件内容复杂,将来不好维护,通常都会重新新建对应的一个页面文件,然后再到 page.ejs
里面引用即可。
下面做一个 关于
页面的例子:
- 首先在根目录资源文件夹创建一个名为
about
的文件夹,再到该文件夹下创建一个index.md
文件,内容为:
---
title: 关于
type: 'about'
---
这是一个关于页面内容
- 到主题文件夹中布局文件夹中创建一个
about.ejs
页面,内容为:
<div class="about-page">
<h1><%= page.title %></h1>
<p><%- page.content %></p>
</div>
- 在
page.ejs
中引入about.ejs
<% if (is_page() && page.type === 'about') { %> // 该文件是其他页面的集合,所以得判断啥情况引入啥文件
<%- partial('about') %>
<% } %>
// 假设引入友情链接文件
<% if (is_page() && page.type === 'links') { %> // is_page() 是啥请看官方文档 - 辅助函数
<%- partial('links') %>
<% } %>
- 最终渲染结果
<!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.ejs
和 tags.ejs
页面是不是就可以使用 site.categories.each...
和 site.tags.each...
用同样的方式给弄出来了。
# 样式文件
如何在样式代码文件中引用主题文件的参数呢?假设主题配置文件中如下:
color: '#c5cae9'
在 ejs 页面中完全可以使用 theme.color
来获取到该属性。
在 Stylus 文件中使用如下:
$main-color = hexo-config('color')
// 或
mainColor = unquote(hexo-config("color"))
# 结语
目前就想到这些了,纯小白肯定是没法看懂的,本文大多属于小坑小提醒类型,最好具备文首所述的那些知识再来折腾比较好。要是有其他啥问题的可以到 我的网站 (opens new window) 评论留言或者找到我的联系方式进行交流,我可能很菜,只是将我写主题时遇到的些磕磕碰碰写出来,万一对谁起作用呢!
广告:
关于评论
评论前请填好“昵称”、“邮箱”这两栏内容,否则不会收到回复,谢谢!