Intro

本文将逐个分析 Acrylic 主题项目中的 layout 文件夹中的文件,包括这些文件中引用到的文件。

本文会为每个文件的代码给出简明的注释,并说明我做出的修改以及修改的理由。

404 文件

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
<div id="error-wrap">
<div class="error-content">
<!-- 设置图片 -->
<div class="error-img" style="background-image: url('<%= theme.errorpage.img %>')"></div>

<div class="error-info">

<!-- 标题 -->
<h1 class="error_title"><%= theme.errorpage.title %></h1>

<div class="error_subtitle">
<!-- 副标题 -->
<%= theme.errorpage.subtitle %>
</div>

<!-- 按钮 -->
<a class="button--animated" href="/">
<i class="<%= theme.errorpage.buttonIcon %>"></i>
<%= __('errorPage.backToHomePage') %>
</a>

</div>

</div>
</div>

<% if(theme.errorpage.recommendList){ %>
<div class="aside-list">
<div class="aside-list-group">

<!-- 最多显示 5 篇推荐文章 -->
<% for (const [index, post] of site.posts.sort('-date').data.entries()){ %>
<% if(index >= 5)break; %>

<div class="aside-list-item">
<!-- 缩略图 -->
<a class="thumbnail" href="<%= url_for(post.path) %>" title="<%= post.title %>">
<img src="<%= post.cover %>" alt="<%= post.title %>">
</a>

<!-- 标题 -->
<div class="content">
<a class="title" href="<%= url_for(post.path) %>" title="<%= post.title %>">
<%= post.title %>
</a>
</div>
</div>

<% } %>

</div>
</div>
<% } %>

注释过后的代码如上所示。这里我们进行了几点小的更改,就是把标题和按钮中的 icon 用变量表示,用户可以在 _config.yml 文件中对其进行修改。然后用 __() 函数来调用显示的内容,并在语言包文件中添加对应的内容,从而使得内容会随着语言的变化而变化。

archieve 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<main class="layout" id="content-inner">

<div id="archive">
<div class="article-sort-title">
<!-- 获取存档页面的标题文本 -->
<%= __('page.archives') %>
<sup>
<!-- 存档页面的文章数量 -->
<%= page.posts.length %>
</sup>
</div>

<!-- 文章 -->
<div class="article-sort">
<%- partial('partial/component/mixins/articleSort') %>
</div>

<!-- 分页元素 -->
<%- partial('partial/component/mixins/pagination') %>
</div>

<!-- 侧边栏 -->
<%- partial('partial/component/aside/aside') %>
</main>

这里获取存档页面标题的方法是使用了模板引擎中的 __() 函数,这个函数用于实现多语言支持,接受一个字符串作为参数,该字符串通常是需要翻译的文本。在使用 __() 函数时,模板引擎会将该字符串传递给国际化库或语言包,以获取相应语言环境下的翻译结果,并将其返回给模板引擎。如果没有找到该字符串的翻译结果,则会返回原始的字符串。

<%- partial('...') %> 是一种模板引擎语法,用于将另一个模板文件中的内容嵌入到当前模板文件中。这种语法通常用于实现页面的组件化和模块化,以及代码的复用和维护。使用 <%- partial('...') %> 语法时,需要传递一个参数,表示需要嵌入的模板文件的路径。该路径通常是相对于当前模板文件的路径,也可以是绝对路径或者基于某个模板库的命名空间(namespace)。模板引擎在处理模板文件时,会自动查找并加载需要嵌入的模板文件,并将其内容嵌入到当前模板文件的对应位置。

articleSort 文件

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
<% let year %>

<% page.posts.data.forEach(post => { %>

<!-- 获取文章的年份,然后判断当前文章的年份是否与上一篇文章的年份相同,如果不同则输出一个年份标题 -->
<% if(year !== moment(post.date).year()){
year = moment(post.date).year() %>
<div class="article-sort-item year"><%= year %></div>
<% } %>

<div class="article-sort-item">

<!-- 输出文章的封面 -->
<a class="article-sort-item-img" href="<%= url_for(post.path) %>" title="<%= post.title %>">
<img src="<%= post.cover %>"
alt="<%= post.title %>">
</a>

<div class="article-sort-item-info">
<!-- 输出文章的标题 -->
<a class="article-sort-item-title" href="<%= url_for(post.path) %>" title="<%= post.title %>"
onclick="window.event.cancelBubble=true;">
<%= post.title %>
</a>

<!-- 输出文章的标签 -->
<div class="article-sort-item-tags">
<% post.tags.data.forEach(tags => { %>
<a class="article-meta__tags" href="<%= url_for(tags.path) %>" onclick="window.event.cancelBubble=true;">
<span class="tags-punctuation">#</span>
<%= tags.name %>
</a>
<% }) %>
</div>

</div>

</div>

<% }); %>

这段代码是一个使用模板引擎编写的存档页面的主体内容,用于按年份对文章进行分类并显示。

代码分为两个主要部分,首先定义了一个变量 year,然后使用循环语句遍历 page.posts.data 数组中的每一篇文章,并根据文章发布年份来对文章进行分组。具体地,使用 moment 库解析文章的发布日期,获取文章的年份,然后判断当前文章的年份是否与上一篇文章的年份相同,如果不同则输出一个年份标题。

在每一篇文章的分组中,通过模板引擎语法输出文章的标题、封面、标签等信息,其中 url_for 函数用于生成文章、标签等链接的 URL 地址。这些信息被包含在一个 HTML 元素中,样式由 CSS 文件定义。

整个代码的执行结果是一个按年份分组的文章列表,其中每篇文章都显示了标题、封面和标签等信息。该代码需要在支持模板引擎的应用程序中运行,以便正确解析模板文件并生成 HTML 页面。

pagination 文件

1
2
3
4
5
6
7
8
9
10
11
<nav id="pagination">
<div class="pagination">
<% const options = {
<!-- 定义分页导航中当前页码两侧应该显示的页码数量 -->
mid_size: 3,
<!-- 定义分页导航中的URL是否需要进行HTML转义,如果为 true,则需要进行转义,否则不需要转义 -->
escape: false
} %>
<%- paginator(options) %>
</div>
</nav>

这段代码是一个用于生成分页导航的模板,使用了模板引擎中的 paginator 函数来生成分页导航的 HTML 代码,并将 HTML 代码插入到模板中的分页导航容器中。

aside 文件

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
<div class="aside-content" id="aside-content">
<%
var noSticky = [], Sticky = [];
if(is_home()){
if(theme.aside.home.noSticky) {
noSticky = theme.aside.home.noSticky.split(',')
}
if(theme.aside.home.Sticky) {
Sticky = theme.aside.home.Sticky.split(',')
}
}
if(is_post()){
if(theme.aside.post.noSticky) {
noSticky = theme.aside.post.noSticky.split(',')
}
if(theme.aside.post.Sticky) {
Sticky = theme.aside.post.Sticky.split(',')
}
}
if(is_page() || is_archive() || is_tag() || is_category()){
if(theme.aside.page.noSticky) {
noSticky = theme.aside.page.noSticky.split(',')
}
if(theme.aside.page.Sticky) {
Sticky = theme.aside.page.Sticky.split(',')
}
}
%>

<!-- 输出固定的模块 -->
<% if(noSticky)noSticky.forEach(item => { %>
<%- partial('partial/component/aside/asideSwitch', {item: item}) %>
<% }); %>

<div class="sticky_layout">
<!-- 输出目录 -->
<% if(page.toc){ %>
<%- partial('partial/component/aside/asideToc') %>
<% } %>
<!-- 输出随页面滚动的模块 -->
<% if(Sticky)Sticky.forEach(item => { %>
<%- partial('partial/component/aside/asideSwitch', {item: item}) %>
<% }); %>
</div>

</div>

在这段代码中,noStickySticky 变量是根据当前页面的类型(首页、文章页、页面等)从主题配置文件中获取的,用于确定哪些侧边栏需要固定在页面上方,哪些需要随页面滚动而移动。

这里我们进行了较大的改动:

  1. 在变量定义时将其初始化为空数组
  2. 在获取配置文件中的值时,判断它是否为空,如果为空则不执行任何操作,否则将其分割为数组

这些改动的目的是为了确保在主题配置文件中即使 noStickysticky 的内容为空,代码也能正常运行而不会出现错误。

category 文件

首先需要说明的是,这个 category 文件不是首页直通的分类页面,而是在那个分类页面选择了一个分类后到达的页面。两者的主要区别是前者没有文章列表。

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
<main class="layout" id="content-inner">

<div id="category">
<div class="category-in-bar">

<!-- 分类条前面的提示 -->
<!-- <div class="category-in-bar-tips"><%= __('page.category') %></div> -->
<div class="article-sort-title">
<!-- 获取存档页面的标题文本 -->
<%= __('page.category') %>
<sup>
<!-- 存档页面的文章数量 -->
<%= page.posts.length %>
</sup>
</div>

<!-- 分类条 -->
<div id="category-bar" style="margin-left: 20px;">
<div class="category-bar-items" id="category-bar-items">
<%- partial('partial/component/home/homeCategoryBar', {select:page.category}) %>
</div>
</div>

</div>

<!-- 当前分类 -->
<div class="article-sort-title"><%= page.category %></div>

<!-- 文章 -->
<div class="article-sort">
<%- partial('partial/component/mixins/articleSort') %>
</div>

<!-- 分页元素 -->
<%- partial('partial/component/mixins/pagination') %>
</div>

<!-- 侧边栏 -->
<%- partial('partial/component/aside/aside') %>
</main>

这里我们注释掉了分类条前面的提示内容,添加了类似 archive 文件中的标题样式,个人感觉这样更好看,而且使得页面之间更具有一致性。

homeCategoryBar 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="category-bar-items" id="category-bar-items">

<!-- <div class="category-bar-item <%= is_home() ? 'select' : '' %>" id="category-bar-home">
<a href="/"><%= __('categoryBar.home') %></a>
</div> -->

<% for(const item of site.categories.data){ %>
<div class="category-bar-item <%= select ? (select === item.name ? 'select' : '') : '' %>">
<a href="<%= url_for(item.path) %>"><%= item.name %></a>
</div>
<% } %>

<div class="category-bar-item <%= select ? (select === '' ? 'select' : '') : '' %>">
<a href="/categories/"><%= __('categoryBar.more') %></a>
</div>

</div>

这里我们注释掉了开头的 “home” 按钮,并把后面的 “more” 按钮转移到了分类条的内部,从而使得 “more” 按钮在被选中时有这和其它按钮一致的特效。同时我们用 __() 函数来调用显示的内容,并在语言包文件中添加对应的内容,从而使得这些内容会随着语言的变化而变化。

index 文件

index 文件就是博客的首页,注释后的代码如下:

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
<div id="home_top">
<!-- 即刻说说 -->
<% if(theme.hometop.bbtime){ %>
<%- partial('partial/component/hometop/bbTimeList') %>
<% } %>
<!-- 技能商标面板、三分类、箴言卡片 -->
<% if(theme.hometop.banner.enable){ %>
<%- partial('partial/hometop') %>
<% } %>
</div>

<main class="layout" id="content-inner">
<div class="recent-posts" id="recent-posts">
<!-- 分类条 -->
<div id="category-bar">
<%- partial('partial/component/home/homeCategoryBar', {select:false}) %>
</div>
<!-- 文章 -->
<% for (const item of page.posts.sort('-date').data){ %>
<%- partial('partial/component/home/postList', {post: item}) %>
<% } %>
<!-- 分页元素 -->
<%- partial('partial/component/mixins/pagination') %>
</div>
<!-- 侧边栏 -->
<%- partial('partial/component/aside/aside') %>
</main>

其中下边部分我们在之前都讨论过了,这里只看即刻说说和技能商标面板等部分。

其中注释后的 hometop 文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="recent-top-post-group" id="recent-top-post-group">
<div class="recent-post-top" id="recent-post-top">

<div id="bannerGroup">
<!-- 技能商标面板 -->
<div id="banners">
<%- partial('partial/component/hometop/groupTag') %>
</div>
<!-- 三分类 -->
<div class="categoryGroup">
<%- partial('partial/component/hometop/categoryGroup') %>
</div>
</div>
<!-- 箴言卡片 -->
<div class="topGroup">
<%- partial('partial/component/hometop/topGroup') %>
</div>

</div>
</div>

我们讲直接讨论具体的三个部分。

bbTimeList 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<% const imageText=__('bbBar.image'); %>
<div class="bbTimeList container" id="bbTimeList">
<i class="bber-logo fa-regular fa-bell icon-bblogo" onclick="pjax.loadUrl('/says/')"></i>
<!-- swiper 容器,可以滚动显示最新动态 -->
<div class="swiper-container swiper-no-swiping swiper-container-initialized swiper-container-vertical swiper-container-pointer-events"
id="bbtalk" tabindex="-1" onclick="pjax.loadUrl('/says/')">
<div class="swiper-wrapper" id="bber-talk">
<!-- 根据站点数据(site.data)中的最新动态列表(essay.essay_list)进行遍历 -->
<% for(i in site.data.essay.essay_list){ %>
<!-- 设置当前遍历到的最新动态的信息,并且限制只展示前十条最新动态 -->
<% const item = site.data.essay.essay_list[i];if(i >= 10)break; %>
<div class="li-style swiper-slide">
<!-- 最新动态的内容,如果存在图片,则内容末尾加上“【图片】” -->
<%= item.image ? item.content + imageText : item.content %>
</div>
<% } %>
</div>
</div>
<i class="bber-gotobb fas fa-arrow-circle-right" title="<%= __('bbBar.viewFullText') %>" onclick="pjax.loadUrl('/says/')"></i>
</div>

这里我们把“【图片】”用一个变量来代替,用于实现多语言支持。

groupTag 文件

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
<div class="banners-title">
<!-- 标题 -->
<div class="banners-title-big">
<%- theme.hometop.banner.title %>
</div>
<!-- 副标题 -->
<div class="banners-title-small">
<%= theme.hometop.banner.subtitle %>
</div>
</div>

<div class="tags-group-all">
<div class="tags-group-wrapper">
<% for(let i = 1; i <= 2; i++){ %>
<!-- 遍历所有键值对 -->
<% for(let j = 0; j < Object.keys(theme.hometop.banner.icon).length; j++){ %>
<% const key = Object.keys(theme.hometop.banner.icon)[j] %>
<% if(j % 2 == 0){ %>
<div class="tags-group-icon-pair">
<% } %>
<div class="tags-group-icon" style="background: <%- theme.hometop.banner.icon[key].color %>">
<img src="<%= theme.hometop.banner.icon[key].img %>" title="<%= key %>">
</div>
<% if(j % 2 == 1 || j == Object.keys(theme.hometop.banner.icon).length - 1){ %>
</div>
<% } %>
<% } %>
<% } %>
</div>
</div>

<!-- 悬停后面板 -->
<a id="banner-hover" onclick="toRandomPost()" data-pjax-state="">
<span class="bannerText"><%= __('homeTop.randomPost') %></span>
<i class="fas fa-arrow-right banner-righticon"></i>
</a>

这里我们用一个变量来代替悬停面板的内容,实现多语言支持。

categoryGroup 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="categoryItem" style="box-shadow: var(--heo-shadow-blue);"><a class="categoryButton CB1 bikan"
href="<%- url_for(theme.hometop.categoryGroup.G1.url) %>">
<span class="categoryButtonText"><%= theme.hometop.categoryGroup.G1.name %></span>
<i class="<%- theme.hometop.categoryGroup.G1.icon %>"></i>
</a>
</div>

<div class="categoryItem" style="box-shadow: var(--heo-shadow-red);">
<a class="categoryButton remen" href="<%- url_for(theme.hometop.categoryGroup.G2.url) %>">
<span class="categoryButtonText"><%= theme.hometop.categoryGroup.G2.name %></span>
<i class="<%- theme.hometop.categoryGroup.G2.icon %>"></i>
</a>
</div>

<div class="categoryItem" style="box-shadow: var(--heo-shadow-green);">
<a class="categoryButton shiyong"
href="<%- url_for(theme.hometop.categoryGroup.G3.url) %>">
<span class="categoryButtonText"><%= theme.hometop.categoryGroup.G3.name %></span>
<i class="<%- theme.hometop.categoryGroup.G3.icon %>"></i>
</a>
</div>

这个文件没有特别需要讲解的地方,内容都可以在主题配置文件中进行修改,样式内容我也都很喜欢,不需要进行更改。

topGroup 文件

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
<style type="text/css">
.topGroup .todayCard::after {
box-shadow: 0 -109px 133px -9px <%- theme.hometop.recommendList.color %> inset;
}

.todayCard-title,
.todayCard-tips,
.topGroup .banner-button {
color: #ffffff;
}

.topGroup .todayCard {
background: <%- theme.hometop.recommendList.color %>;
}
</style>

<!-- 利用模板语法遍历站点中的文章,并且只展示推荐文章,最多展示前六篇 -->
<% for (const post of site.posts.data.filter(item => item.recommend === true).slice(0,6)){ %>
<div class="recent-post-item">
<!-- 文章封面 -->
<div class="post_cover left_radius">
<a href="<%= url_for(post.path) %>"
title="<%= post.title %>">
<span class="recent-post-top-text"><%= __('homeTop.rec') %></span>
<img class="post_bg"
alt="<%= post.title %>" src="<%= post.cover %>" loading="lazy">
</a>
</div>
<!-- 文章标题 -->
<div class="recent-post-info">
<a class="article-title" href="<%= url_for(post.path) %>"
title="<%= post.title %>"><%= post.title %>
</a>
</div>
</div>
<% } %>

<div class="todayCard" id="todayCard" onclick="pjax.loadUrl('<%= theme.hometop.recommendList.url %>')"
style="z-index: 1;">
<div class="todayCard-info">
<!-- 上标题与标题 -->
<div class="todayCard-tips"><%= theme.hometop.recommendList.supTitle %></div>
<div class="todayCard-title"><%= theme.hometop.recommendList.title %></div>
</div>
<!-- 图片 -->
<% const today = new Date().getDay(); %>
<% const imageFolder = theme.hometop.recommendList.imageFolderPath; %>
<% const image = imageFolder + today + '.jpg'; %>
<div class="todayCard-cover nolazyload"
style="background: url('<%= image %>') no-repeat center /cover">
</div>
<!-- 更多推荐按钮 -->
<div class="banner-button-group"><a class="banner-button"
onclick="window.event.cancelBubble=true;B612.hideTodayCard();">
<i class="fas fa-circle-plus"></i>
<span class="banner-button-text"><%= __('homeTop.recommendList') %></span>
</a>
</div>
</div>

这个文件一共有三个部分。

第一部分定义了一些 CSS 样式,这部分出现在这里可能是因为我们需要在主题配置文件中设置今日卡片的颜色,在对 CSS 有更多了解前暂时不对其进行更改。

第二部分设置了推荐文章面板。这部分代码利用模板语法遍历站点中的文章,并且只展示推荐文章,最多展示前六篇。遍历到每一篇文章时,会渲染一个 HTML 结构,包括文章的封面图、文章标题等信息,并且在封面图上方加上一个“荐”字表示该文章为推荐文章。我们的修改是用一个变量来代替“荐”,用于实现多语言支持。

第三部分是对今日卡片的设置。对图片部分我们的更改是让它从主题配置文件中传入一个文件夹,而不像之前那样只传入一张图片。新增了部分代码会根据当天是周几来调用对应的图片。更多推荐的按钮内容也被变量代替,用于实现多语言支持。

layout 文件

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
<!DOCTYPE html>
<html lang="<%= config.language %>" data-theme="light">
<!-- 网页头部,包括网页名称等,显示在浏览器的标签页上 -->
<head>
<!-- {cache: true} 表示使用缓存,可以提高页面的加载速度 -->
<%- partial('partial/head', {cache: true}) %>
</head>

<!-- 页面的主体部分,显示出来的页面 -->
<body id="body">
<!-- 根据主题配置文件中的 loading.fullpage 值来决定是否显示全屏加载动画 -->
<% if(theme.loading.fullpage){ %>
<%- partial('partial/loading', {cache: true}) %>
<% } %>

<!-- 控制器和侧边栏 -->
<%- partial('partial/console', {cache: true}) %>
<%- partial('partial/sidebar', {cache: true}) %>

<% if(page.type !== '404'){ %>
<!-- body-wrap 用于包含文章主体或页面主体,根据当前页面类型的不同,它的类名也不同 -->
<div class="<%= is_post() ? 'post' : 'page' %>" id="body-wrap">
<!-- 页面的头部 -->
<%- partial('partial/header') %>
<!-- 页面的主体 -->
<%- body %>
<!-- 页脚 -->
<footer id="footer">
<%- partial('partial/footer', {cache: true}) %>
</footer>
</div>
<% }else{ %>
<!-- 404 页面没有头部和页脚 -->
<div class="error" id="body-wrap">
<%- body %>
</div>
<% } %>

<%- partial('partial/body', {cache: true}) %>
<%- partial('partial/component/third-party/search/index', {cache: true}) %>
<%- partial('partial/component/third-party/music', {cache: true}) %>
</body>
</html>

这个文件是 Hexo 主题的页面模板,它包含了网站的 HTML 结构和部分页面组件的渲染。接下来将分别讲解每个部分的内容。

head 文件

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
<!-- <meta>:元信息,提供有关文档的信息,如字符集、关键词、描述等 -->
<!-- <title>:文档的标题,显示在浏览器的标签页上 -->
<!-- <noscript>:用于在没有JavaScript支持的情况下提供替代内容 -->
<!-- <link>:链接标签,用于定义文档和外部资源之间的关系,如样式表、图标等 -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%
var title = '', subtitle = ''
if(is_home()){title = config.title}
else if(is_archive())title = 'Archive ' + (page.year || '')
else if(is_category())title = 'Category ' + page.category
else if(is_tag())title = 'Tag ' + page.tag
else if(page.type === '404')title = '404 ' + theme.errorpage.text
else if(is_page() || is_post())title = page.title
if(is_home())subtitle = config.subtitle ? ' - ' + config.subtitle : ''
else subtitle = title ? ' | ' + config.title : config.title
var description = page.description || page.excerpt || config.description
-%>
<!-- 网页信息 -->
<title><%= title + subtitle %></title>
<meta name="apple-mobile-web-app-title" content="<%= config.title %>">
<noscript>Please make sure Javascript enabled</noscript>
<link rel="icon" type="image/x-icon" href="<%= url_for(theme.site.siteIcon) %>">
<link rel="apple-touch-icon" href="<%= url_for(theme.site.siteIcon) %>">
<link rel="bookmark" href="<%= url_for(theme.site.siteIcon) %>">
<link rel="apple-touch-icon-precomposed" sizes="180x180" href="<%= url_for(theme.site.siteIcon) %>">
<meta name="description" content="<%= description %>">
<meta name="theme-color" content="#1C1C1F">
<!-- 网页资源 -->
<link rel="stylesheet" href="/css/var.css">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/custom.css">
<%- partial('partial/component/inject/head', {cache: true}) %>
<!-- 判断是否有扩展属性 extends.head,使用循环语句输出对应的 HTML 代码 -->
<% if (theme.extends.head) for (const i of theme.extends.head){ %>
<%- i %>
<% } %>
<!-- 将Hexo配置信息导出为一个全局对象 -->
<%- export_config() %>

这段代码是 HTML 的头部(head)部分,包含了一些元信息(meta),链接(link)和样式表(stylesheet)等标签。

代码中的 JavaScript 变量 titlesubtitledescription 根据当前页面的类型和设置动态生成标题和描述等元信息。

样式表

样式表(stylesheet)标签是用于在 HTML 页面中引入外部样式表文件的标签,它是实现页面样式和布局的重要工具之一。它有以下几个属性:

  • href:引用外部样式表文件的 URL。该属性是必需的,没有它,样式表就无法被引入到页面中。
  • rel:定义当前文档与被链接文档之间的关系。在样式表中,它的值必须是 “stylesheet”。
  • type:定义被链接文档的 MIME 类型。在样式表中,它的值必须是 “text/css”。
  • media:指定样式表所适用的媒体类型。默认情况下,它的值是 “screen”,表示样式表适用于计算机屏幕。还可以指定其他类型,比如 “print”(适用于打印设备)、”all”(适用于所有设备)、”speech”(适用于屏幕阅读器)等等。

当浏览器解析 HTML 页面时,遇到样式表标签时,会通过 href 属性指定的 URL 发送 HTTP 请求,下载样式表文件。然后,浏览器会解析样式表,将其中的样式规则应用于 HTML 元素,从而改变它们的外观和布局。样式表可以包含很多种类型的选择器、属性和值,用来定义各种样式规则。样式表的优点在于它能够实现样式和布局的模块化和可重用性,从而提高代码的可维护性和可扩展性。同时,它还可以提高网页的加载速度,因为外部样式表文件可以被缓存,避免了每个页面都需要重新下载样式表的情况。

loading 文件

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
<!-- 包裹加载动画的 div 标签 -->
<div id="loading-box" onclick="preloader.endLoading();" class style="zoom: 1;">
<!-- 背景 -->
<div class="loading-bg">
<!-- 加载图标 -->
<img class="loading-img nolazyload" alt="loading image" src="<%= theme.site.icon %>">
</div>
</div>

<script>
const preloader = {
// 在页面加载完毕时调用,,将包裹加载动画的 div 标签的 class 属性设置为 "loaded",从而隐藏加载动画
endLoading: () => {
document.getElementById('loading-box').classList.add("loaded")
},
// 在页面开始加载时调用,将 class 属性设置为不含 "loaded",从而显示加载动画
initLoading: () => {
document.getElementById('loading-box').classList.remove("loaded")
},
// 将整个页面的 class 属性设置为 "pace-done",用处未知
removePaceDone: () => {
document.getElementById('body').classList = 'pace-done'
}
}
// 事件监听器
// load 事件是当一个页面及其所有依赖资源(如样式表、脚本、图片等)都加载完成后触发的事件,是浏览器自带的事件
// pjax 是一种无刷新页面加载技术,通过异步加载局部页面内容,同时保留原有页面的部分元素,以达到更快速的页面切换效果
// 当 pjax 开始发送请求时,会触发 pjax: send 事件,表示 pjax 开始工作并开始加载页面
// 当 pjax 完成请求,局部页面内容加载成功后,会触发 pjax: complete 事件,表示 pjax 已完成工作并成功加载了局部页面内容
// 当页面加载完成时,将会调用 endLoading 方法隐藏加载动画
window.addEventListener('load',()=> { preloader.endLoading() })
// 当页面开始加载时,将会调用 initLoading 方法显示加载动画
document.addEventListener('pjax:send', () => { preloader.initLoading() })
document.addEventListener('pjax:complete', () => { preloader.endLoading() })
</script>

<!-- 加载动画的样式 -->
<style>
#loading-box {
-webkit-user-select: none;
}

#loading-box .loading-bg {
display: flex;
width: 100%;
height: 100%;
position: fixed;
background: var(--heo-background);
z-index: 1999;
opacity: 1;
transition: 0.2s;
pointer-events: all;
animation: showLoading 0.3s 0s backwards;
}

#loading-box.loaded .loading-bg {
pointer-events: none;
transition: 0.2s;
animation: hideLoading 0.3s 0s forwards;
}

#loading-box .loading-img {
width: 100px;
margin: auto;
animation-duration: 0.2s;
animation-name: loadingAction;
animation-iteration-count: infinite;
animation-direction: alternate;
}

@keyframes loadingAction {
from {
opacity: 1;
}

to {
opacity: 0.6;
}
}

@keyframes hideLoading {
from {
opacity: 1;
}

to {
opacity: 0;
}
}

@keyframes showLoading {
from {
opacity: 0;
}

to {
opacity: 1;
}
}
</style>

这段代码实现了一个网页加载动画的效果,主要包括三个部分:HTML、JavaScript 和 CSS。这里暂时没有什么特别需要注意或修改的地方。可能把 HTML、JavaScript 和 CSS 三部分分别放到不同的文件内管理会比较好。

console 文件

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
<div id="console" style="zoom: 1;" class="">
<!-- 用于关闭控制台的按钮 -->
<div class="close-btn" onclick="B612.hideConsole()" href="javascript:void(0);">
<i class="fas fa-circle-xmark"></i>
</div>

<div class="button-group">
<!-- 用于切换显示模式的开关 -->
<div class="console-btn-item">
<a class="darkmode_switchbutton" onclick="B612.switchDarkMode()" title="<%= __('console.switchDarkMode') %>"
href="javascript:void(0);" rel="external nofollow" data-pjax-state="">
<i class="fas fa-moon" style="font-size: 1rem;"></i>
</a>
</div>
<!-- 用于控制音乐播放 -->
<% if(theme.thirdparty.aplayer.enable){ %>
<div class="console-btn-item" id="consoleMusic" onclick="B612.musicToggle()" title="<%= __('console.consoleMusic') %>">
<a class="music-switch">
<i class="fas fa-music"></i>
</a>
</div>
<% } %>
<!-- 用于切换帧率显示 -->
<div class="console-btn-item" id="consoleFPS" onclick="B612.FPSToggle()" title="<%= __('console.consoleFPS') %>">
<a class="FPS-switch" data-pjax-state="">
<i class="fas fa-gauge"></i>
</a>
</div>
</div>

<!-- 用于在点击控制台外部时隐藏控制台 -->
<div class="console-mask" onclick="B612.hideConsole()" href="javascript:void(0);" rel="external nofollow"></div>
</div>

这个文件的代码对应的是点击右上角的控制器按钮后打开的页面,注释后的代码如上,其中用于切换帧率显示的开关暂时没有用处,因为 B612.FPSToggle() 并没有被创建。我们把 title 的内容都用变量表示,用于实现多语言支持。

该文件中我认为最重要的就是用于切换显示模式的开关。首先我修改了 B612.switchDarkMode() 函数(位于 source/js/main.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
static isDarkMode() {
return document.documentElement.getAttribute('data-theme') === 'dark';
}
static updateDarkModeSwitchButton() {
const isDark = this.isDarkMode();
const switchButtons = document.querySelectorAll('.darkmode_switchbutton i');
switchButtons.forEach(button => {
if (isDark) {
button.classList.remove('fa-moon');
button.classList.add('fa-sun');
} else {
button.classList.remove('fa-sun');
button.classList.add('fa-moon');
}
});
}
static switchDarkMode() {
if (this.isDarkMode()) {
// 切换到浅色模式
document.documentElement.setAttribute('data-theme', 'light')
localStorage.setItem('theme', 'light')
utils.snackbarShow(GLOBALCONFIG.lang.theme.light, false, 2000)
} else {
// 切换到暗黑模式
document.documentElement.setAttribute('data-theme', 'dark')
localStorage.setItem('theme', 'dark')
utils.snackbarShow(GLOBALCONFIG.lang.theme.dark, false, 2000)
}
this.updateDarkModeSwitchButton();
}

这份更改实现了以下目的:

  1. 开关的 icon 可以根据现在的显示模式进行变化。如果当前是深色模式,开关的 icon 就是太阳的形状,否则就是月亮的形状。
  2. 当改变显示模式的时候,会对所有 class=darkmode_switchbutton 的图标进行变更。因为我觉得这个开关会被经常用到,因此它不应该只出现在 console 界面,还应该在网页的 header 部分出现。因此我们会有不止一个开关需要进行更改。
  3. 当网页刷新时会根据当前的显示模式选择对应的 icon。这份更改需要在 B612.initTheme() 方法中添加一行新的代码:this.updateDarkModeSwitchButton();

为了实现上面的第二个目的,我们需要在 partial/component/nav/right.ejs 文件中添加添加以下代码:

1
2
3
4
5
6
7
8
9
<% if(theme.nav.right.darkMode){ %>
<!-- 用于切换显示模式的开关 -->
<div class="nav-button">
<a class="darkmode_switchbutton" onclick="B612.switchDarkMode()" title="<%= __('console.switchDarkMode') %>"
href="javascript:void(0);" rel="external nofollow" data-pjax-state="">
<i class="fas fa-moon" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

并且在主题配置文件的 theme.nav.right 条目下添加 darkMode 项目,并设置为 true,即 darkMode: true

此时两个开关的显示仍可能有问题,就是它们的颜色可能会在点击后变得异常,这需要到 source/css/main.css 文件中注释掉以下代码:

1
2
3
4
5
6
7
8
/* [data-theme=dark] #page-header.not-top-img #nav .darkmode_switchbutton {
color: var(--heo-yellow);
} */

/* [data-theme=dark] a.darkmode_switchbutton {
background: var(--heo-orange) !important;
color: var(--heo-white) !important;
} */

做完以上修改后,基本可以达到我残缺的审美下渴望的效果。

但还有一个问题。如果在网页刷新前不是深色模式,在刷新后网页会自动切换到深色模式。

始终没能找到这个文件对应的是博客中的哪一部分,因此先跳过这一份文件。

header 文件

这个文件的内容只有以下部分,我们直接看它引用的文件。

1
2
3
4
5
6
<header class="<%= is_post() ? 'post-bg' : 'not-top-img' %>" id="page-header">
<%- partial('partial/nav', {cache: true}) %>
<% if(is_post()){ %>
<%- partial('partial/component/post/postMeta') %>
<% } %>
</header>
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
<nav id="nav" class="show">
<div id="nav-group">

<div id="blog_name">
<!-- 左侧图标 -->
<% if(theme.nav.left.enable){ %>
<div class="back-home-button" tabindex="-1">
<i class="fa-solid fa-feather-pointed"></i>
<!-- <i class="back-home-button-icon fas fa-grip-vertical"></i> -->
<%- partial('partial/component/nav/left') %>
</div>
<% } %>
<!-- 返回主页按钮 -->
<a id="site-name" href="/" title="<%= __('nav.backToHomePage') %>">
<% if (theme.site.name.class === 'i_class') { %>
<i class="<%= theme.site.name.custom %>" style="font-size: 25px;"></i>
<% } else if (theme.site.name.class === 'img') { %>
<i style="background-image: url('<%= url_for(theme.site.name.custom) %>')"></i>
<% } else if (theme.site.name.class === 'text') { %>
<span class="heoicon"><%= theme.site.name.custom %></span>
<% } %>
</a>
</div>
<!-- 页面不在顶部时出现的页面标题 -->
<div id="page-name-mask">
<div id="page-name">
<a id="page-name-text" onclick="B612.toTop()"><%= page.title || config.title %></a>
</div>
</div>
<!-- 页面在顶部时出现的可选菜单 -->
<div id="menus">
<%- partial('partial/component/nav/menu') %>
</div>

<div id="nav-left">
<div id="fps-group">
<div id="fps">145</div>
<span class="fpsText">FPS</span>
</div>
</div>
<!-- 导航栏右侧图标组 -->
<div id="nav-right">
<%- partial('partial/component/nav/right') %>
</div>

</div>
</nav>

其中 FPS 的部分没明白对应博客中的哪一部分,暂时不做分析。

left 文件

这个文件对应的是页面导航栏左侧的图标被悬停后显示的菜单,我觉得没什么需要修改的部分。

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
<div class="back-menu-list-groups">
<% for(const x in theme.nav.left.menu){ %>
<% const groupData=theme.nav.left.menu[x] %>
<div class="back-menu-list-group">
<!-- 每个分组的标题 -->
<div class="back-menu-list-title">
<%= x %>
</div>

<!-- 每个分组的菜单列表 -->
<div class="back-menu-list">
<% for(const y in groupData){ %>
<% const data=groupData[y] %>

<!-- 每个菜单项,包含图标和文字 -->
<a class="back-menu-item" href="<%= url_for(data.url) %>" rel="external nofollow" title="<%= y %>">
<img class="back-menu-item-icon" src="<%= data.icon %>" loading="lazy">
<span class="back-menu-item-text">
<%= y %>
</span>
</a>

<% } %>
</div>

</div>
<% } %>
</div>

这个文件是在页面头部的中间的菜单部分,注释后的代码如下:

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
<div class="menus_items">
<!-- 循环遍历 theme.nav.menu 对象 -->
<% for(const x in theme.nav.menu){ %>
<!-- 依次生成每个菜单项 -->
<div class="menus_item">
<!-- 如果 url 字段填写 false 则开启二级菜单 -->
<% if(!theme.nav.menu[x].url){ %>
<a class="site-page" href="javascript:void(0);" rel="external nofollow">
<span> <%= x %></span>
</a>
<!-- 二级菜单 -->
<ul class="menus_item_child">
<% for(y in theme.nav.menu[x].child){ %>
<li>
<a class="site-page child" href="<%- url_for(theme.nav.menu[x].child[y].url) %>">
<i class="<%- theme.nav.menu[x].child[y].icon %>"></i>
<span> <%= y %></span>
</a>
</li>
<% } %>
</ul>
<!-- 如果填写 url 链接,可以跳转到其他地方,并关闭二级菜单 -->
<% } else { %>
<a class="site-page not-child" href="<%- url_for(theme.nav.menu[x].url) %>" rel="external nofollow">
<span> <%= x %></span>
</a>
<% } %>
</div>
<% } %>
</div>

其中我比较在意的是菜单文字之间的空格,我因为用的英语菜单,会感觉很奇怪。因此到 source/css/main.css 文件中注释掉了以下代码:

1
2
3
4
5
6
7
8
9
10
#menus>div.menus_items>div>a {
/* letter-spacing: 0.3rem; */
/* padding-left: 0.5rem; */
font-weight: bold;
padding-top: 0;
padding-bottom: 0;
height: 35px;
line-height: 35px;
border-radius: 40px;
}

其中 letter-spacing 是文字间的空隙值;padding-left 似乎是为了让文字在阴影框居中而设计的,但其实注释掉后文字就自动居中了。

right 文件
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
<% if(theme.nav.right.travellings){ %>
<!-- 判断是否需要显示“友链”按钮,如果需要就显示 -->
<div class="nav-button only-home" id="travellings_button">
<a class="site-page" target="_blank" rel="noopener external nofollow" href="https://www.travellings.cn/go.html" title="<%= __('nav.travellings') %>" width="120">
<i class="fa-solid fa-train-subway" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

<% if(theme.nav.right.darkMode){ %>
<!-- 判断是否需要显示“黑暗模式”按钮,如果需要就显示 -->
<div class="nav-button">
<a class="darkmode_switchbutton" onclick="B612.switchDarkMode()" title="<%= __('console.switchDarkMode') %>"
href="javascript:void(0);" rel="external nofollow" data-pjax-state="">
<i class="fas fa-moon" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

<% if(theme.nav.right.random){ %>
<!-- 判断是否需要显示“随机文章”按钮,如果需要就显示 -->
<div class="nav-button" id="randomPost_button">
<a class="site-page" onclick="toRandomPost()"
title="<%= __('nav.randomPost') %>">
<i class="fas fa-shuffle" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

<% if(theme.thirdparty.search.algolia_search.enable || theme.thirdparty.search.local_search.enable){ %>
<!-- 判断是否需要显示“站内搜索”按钮,如果需要就显示 -->
<div class="nav-button" id="search-button">
<a class="site-page social-icon search" title="<%= __('nav.search') %>"
href="javascript:void(0);" rel="external nofollow">
<i class="fas fa-magnifying-glass" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

<% if(theme.nav.right.console){ %>
<!-- 判断是否需要显示“中控台”按钮,如果需要就显示 -->
<div class="nav-button" id="nav-console">
<a class="console_switchbutton" onclick="B612.showConsole()"
title="<%= __('nav.console') %>" href="javascript:void(0);" rel="external nofollow">
<i class="fas fa-bars-progress" style="font-size: 1rem;"></i>
</a>
</div>
<% } %>

<!-- 下滑时出现在 nav 右侧的“回到顶部”按钮 -->
<div class="nav-button" id="nav-totop" onclick="B612.toTop()">
<a class="totopbtn">
<span id="percent">0</span>
<i class="fas fa-arrow-up"></i>
</a>
</div>

<div class="nav-button" id="toggle-menu">
<a class="site-page">
<i class="fas fa-bars fa-fw" style="font-size: 1rem;"></i>
</a>
</div>

最后一部分没在博客中找到对应的图标,暂时不明白它的作用。

友链中的链接应该是需要修改的,但因为我应该会在之后把这个功能关闭,就懒得动它了。

深色模式的开关前面已经提过,这里不再重复。

回到顶部按钮花了较长时间。先是把 source/js/main.js 文件中的 percent() 函数进行了结构上的优化,变得更具有阅读性:

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
const percent = () => {
// 获取当前滚动条位置
const scrollTop = document.documentElement.scrollTop || window.pageYOffset;
// 获取文档根节点
const docElem = document.documentElement;
// 获取文档body节点
const body = document.body;
// 获取文档body高度
const bodyScrollHeight = body.scrollHeight;
// 获取文档根节点高度,取最大值
const docScrollHeight = Math.max(docElem.scrollHeight, docElem.offsetHeight, bodyScrollHeight, body.clientHeight, docElem.clientHeight);
// 计算当前滚动位置百分比
const percentResult = Math.round(scrollTop / (docScrollHeight - docElem.clientHeight) * 100);
// 获取百分比按钮节点
const percentBtn = document.querySelector("#percent");
// 获取“返回顶部”按钮节点
const navTotop = document.querySelector("#nav-totop");
// 获取需要隐藏的元素节点
const needEndHide = document.querySelectorAll(".needEndHide");

// 计算可视区域底部位置
const visibleBottom = window.scrollY + docElem.clientHeight;
// 获取事件监听器节点
const eventlistner = document.getElementById('post-tools') || document.getElementById('footer');
// 计算事件监听器垂直中心位置
const centerY = eventlistner.offsetTop + (eventlistner.offsetHeight / 2);

// 如果事件监听器中心位置小于可视区域底部位置或者滚动位置百分比大于90,则显示“返回顶部”按钮和需要隐藏的元素,并隐藏百分比按钮
if (centerY < visibleBottom || percentResult > 90) {
navTotop.classList.add("long");
// percentBtn.innerHTML = GLOBALCONFIG.lang.backtop;
percentBtn.innerHTML = "End";
needEndHide.forEach(item => item.classList.add("hide"));
} else {
// 否则隐藏“返回顶部”按钮和需要隐藏的元素,显示百分比按钮
navTotop.classList.remove("long");
if (percentResult >= 0) {
percentBtn.innerHTML = percentResult;
needEndHide.forEach(item => item.classList.remove("hide"));
}
}
}

其次是当百分比大于 90 的情况下,注释掉一行原有的代码,该代码会导致显示出现 “undefined”,原因暂且不明。

postMeta 文件

文件注释后的代码如下:

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
<!-- 显示文章的封面 -->
<div class="coverdiv" id="coverdiv">
<img id="post-cover" class="nolazyload" src="<%= page.cover %>" alt="cover">
</div>

<div id="post-info">
<!-- 显示文章的元信息 -->
<div id="post-firstinfo">
<div class="meta-firstline">
<!-- 显示文章的版权协议信息 -->
<a class="post-meta-original" title="<%= __('postmeta.original', page.cc) %>">
<%= page.cc %>
</a>
<!-- 显示文章的分类信息 -->
<% if (page.categories.data.length) { %>
<span class="post-meta-categories">
<% page.categories.data.forEach(item => { %>
<a class="post-meta-categories" href="<%= url_for(item.path) %>"><%= item.name %></a>
<% }) %>
</span>
<% } %>
<!-- 显示文章的标签信息 -->
<div class="tag_share">
<div class="post-meta__tag-list">
<% page.tags.data.forEach(tags => { %>
<a class="post-meta__tags" href="<%= url_for(tags.path) %>">
<span class="tags-punctuation">#</span>
<span class="tags-name"><%= tags.name %></span>
</a>
<% }) %>
</div>
</div>
</div>
</div>

<!-- 显示文章的标题 -->
<h1 class="post-title"><%= page.title %></h1>

<div id="post-meta">
<div class="meta-secondline">
<!-- 显示文章的发布/更新时间 -->
<span class="post-meta-date" title="<%= __('postmeta.posted') %>">
<i class="post-meta-icon fa-fw fas fa-calendar"></i>
<time datetime="<%= moment(page.date).format() %>"></time>
</span>
<% if(theme.post.meta.updated){ %>
<span class="post-meta-date" title="<%= __('postmeta.updated') %>">
<i class="post-meta-icon fa-solid fa-clock-rotate-left"></i>
<time datetime="<%= moment(page.updated).format() %>"></time>
</span>
<% } %>
<!-- 显示文章的作者 IP 归属地信息 -->
<% if(theme.post.meta.locate){ %>
<span class="post-meta-position" title="<%= __('postmeta.locate', page.locate) %>">
<i class="fas fa-location-dot post-meta-icon"></i>
<span><%= page.locate %></span>
</span>
<% } %>
<!-- 显示文章的字数信息 -->
<% if(theme.thirdparty.wordcount){ %>
<% if(theme.post.meta.wordcount){ %>
<span class="post-meta-wordcount" title="<%= __('postmeta.wordCount') %>">
<i class="fas fa-file-word fa-fw post-meta-icon"></i>
<span class="word-count"><%= wordcount(page.content) %></span>
</span>
<% } %>
<!-- 显示文章的阅读耗时信息 -->
<% if(theme.post.meta.readtime){ %>
<span class="post-meta-time" title="<%= __('postmeta.readTime') %>">
<i class="fas fa-clock post-meta-icon"></i>
<span><%= min2read(page.content) %> min</span>
</span>
<% } %>
<% } %>
<!-- 显示文章的浏览量信息 -->
<% if(theme.thirdparty.busuanzi.enable && theme.post.meta.pv){ %>
<span class="post-meta-pv" title="<%= __('postmeta.readNum') %>">
<i class="fab fa-hotjar fa-fw post-meta-icon"></i>
<% if(theme.thirdparty.busuanzi.usecomment){ %>
<span id="twikoo_visitors"><i class="fas fa-spinner fa-pulse"></i></span>
<% }else{ %>
<span id="busuanzi_value_page_pv"><i class="fas fa-spinner fa-pulse"></i></span>
<% } %>
</span>
<% } %>
<!-- 显示文章的评论数 -->
<% if(theme.post.meta.comment && page.comment){ %>
<span class="post-meta-commentcount" title="<%= __('postmeta.commentNum') %>">
<i class="fa-solid fa-message"></i>
<a href="#post-comment">
<span id="twikoo-count"><i class="fas fa-spinner fa-pulse"></i></span>
</a>
</span>
<% } %>
</div>
</div>
</div>
<!-- 显示一个波浪形的背景图案 -->
<%- partial('partial/component/post/wave', {cache: true}) %>

其中 wave 文件可以看作是插件,我也很喜欢它,不需要更改,不再仔细研究。

其他部分我把 title 的内容都用变量表示,用于实现多语言支持。

文件注释后的代码如下:

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
<!-- 页脚的联系方式,可以在主题配置文件进行修改 -->
<div id="footer_deal">
<% for(const i in theme.footer.information.left){ %>
<a class="deal_link" href="<%= url_for(theme.footer.information.left[i].url) %>" rel="external nofollow" title="<%= i %>">
<i class="<%= theme.footer.information.left[i].icon %>"></i>
</a>
<% } %>
<img class="footer_mini_logo" src="<%= theme.site.icon %>"
title="<%= __('footer.backTop') %>" onclick="B612.toTop()">
<% for(const i in theme.footer.information.right){ %>
<a class="deal_link" href="<%= url_for(theme.footer.information.right[i].url) %>" rel="external nofollow" title="<%= i %>">
<i class="<%= theme.footer.information.right[i].icon %>"></i>
</a>
<% } %>
</div>
<!-- 页脚的分组内容,可以在主题文件中进行配置 -->
<div id="heo-footer" style="display: <%= theme.footer.group.enable ? 'block' : 'none' %>;">
<% for(const x in theme.footer.group.list){ %>
<div class="footer-group">
<h3 class="footer-title"><%= x %></h3>
<div class="footer-links">
<% for(const y in theme.footer.group.list[x]){ %>
<a class="footer-item" href="<%= url_for(theme.footer.group.list[x][y]) %>" title="<%= y %>" rel="noopener external nofollow noreferrer"><%= y %></a>
<% } %>
</div>
</div>
<% } %>
<% if(theme.footer.randomlink){ %>
<div class="footer-group">
<div class="footer-title-group">
<h3 class="footer-title"><%= __('footer.friends') %></h3>
<a class="random-friends-btn" href="javascript:randomLinksList();" rel="external nofollow">
<i class="fa-solid fa-arrow-rotate-right"></i>
</a>
</div>
<div class="footer-links" id="friend-links-in-footer">
</div>
</div>
<% } %>
</div>
<!-- 主题的版权信息和一些链接 -->
<div id="footer-section">
<div class="footer-section-links">

<div class="footer-section-left">
<div id="footer-section-tips">
<div class="copyright">©<%= moment(theme.aside.siteinfo.runtime).year() %> - <%= new Date().getFullYear() %> By
<a class="footer-section-link" href="/" rel="external nofollow"><%= config.author %></a>
<!-- <a class="footer-section-link" href="https://github.com/hexo-theme-Acrylic/Hexo-Theme-Acrylic-Next" rel="external nofollow" target="_blank"> Theme By Acrylic-Next</a> -->
</div>
</div>
</div>

<div class="footer-section-right">
<a class="footer-section-link" href="/rss/"><%= __('footer.subscribe') %></a>
<% if(theme.site.icp){ %>
<a class="footer-section-link" target="_blank" href="https://beian.miit.gov.cn/#/Integrated/index" rel="noopener external nofollow noreferrer noopener">
<%= theme.site.icp %>
</a>
<% } %>
<a class="footer-section-link cc" target="_blank" href="https://creativecommons.org/licenses/by/4.0/" title="<%= __('footer.licenses') %>">
<i class="fa-solid fa-closed-captioning"></i>
</a>
</div>
</div>
</div>

其中联系方式的内容可以在主题配置文件中进行配置,我认为这里不需要什么修改。但是有位大佬对这一部分有不同意见,详细内容可以参考这篇文章

版权信息我不太懂 icp 该怎么设置,也就没弄。

订阅部分也是空链接,需要后期配置。

body 文件

文件注释后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div>
<!-- 引入 JavaScript 文件 -->
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
<!-- 模板片段 -->
<%- partial('partial/component/inject/body', {cache: true}) %>
<%- partial('partial/component/third-party/pjax', {cache: true}) %>
<!-- 用于插入一些自定义的 HTML 或 JavaScript 代码,以扩展网站的功能或者样式 -->
<% if (theme.extends.body) for (const i of theme.extends.body){ %>
<%- i %>
<% } %>
<!-- 用于渲染宇宙背景效果 -->
<% if (theme.universe.enable){ %>
<canvas id="universe"></canvas>
<script type="text/javascript" src="/js/universe.js"></script>
<% } %>
</div>

其中用于渲染宇宙背景效果的代码是我们后期加入的,详细操作可以参考这里

index 文件

文件注释后代码如下:

1
2
3
4
5
6
7
8
9
10
11
<% if(theme.thirdparty.search.local_search.enable){ %>
<%- partial('partial/component/third-party/search/local-search') %> <!-- 加载本地搜索组件 -->
<%- css('/css/search/local-search.css') %> <!-- 加载本地搜索的样式表 -->
<%- js('/js/extend/search/local-search.js') %> <!-- 加载本地搜索的 JavaScript 文件 -->
<% } %>
<% if(theme.thirdparty.search.algolia_search.enable){ %>
<%- partial('partial/component/third-party/search/algolia-search') %> <!-- 加载Algolia搜索组件 -->
<%- css('/css/search/algolia-search.css') %> <!-- 加载Algolia搜索的样式表 -->
<%- js('/js/extend/search/algolia-search.js') %> <!-- 加载Algolia搜索的 JavaScript 文件 -->
<% } %>

music 文件

文件注释后代码如下:

1
2
3
4
5
6
7
8
<div class="needEndHide" id="nav-music" onclick="B612.musicToggle()">
<!-- 音乐暂停时的悬停提示 -->
<div id="nav-music-hoverTips">
<%= __('musicPaused') %>
</div>
<meting-js id="<%= theme.thirdparty.aplayer.id %>" server="<%= theme.thirdparty.aplayer.server %>" type="playlist"
mutex="true" preload="none" data-lrctype="0" order="random" theme="var(--heo-main)"></meting-js>
</div>

Page 文件

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
<main class="layout <%= page.aside ? '' : 'hide-aside' %>" id="content-inner">
<div id="page">
<% switch (page.type) {
case 'categories': %>
<%- partial('page/categories') %>
<% break;
case 'tags': %>
<%- partial('page/tags') %>
<% break;
case 'links': %>
<%- partial('page/links') %>
<% break;
case 'about': %>
<%- partial('page/about') %>
<% break;
case 'says': %>
<%- partial('page/says') %>
<% break;
case 'album': %>
<%- partial('album/album') %>
<% break;
case 'album_detail': %>
<%- partial('album/album_detail') %>
<% break;
default: %>
<%- partial('page/page') %>
<% break;
} %>
<% if(page.comment){ %>
<%- partial('partial/component/third-party/comments/comment') %>
<% } %>
</div>
<% if(page.aside){ %>
<%- partial('partial/component/aside/aside') %>
<% } %>
</main>

这段代码很直观,会根据页面的类型分配具体的页面代码,无需注释。

接下来依次分析各个页面代码。

categories 文件

注释后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 页面标题 -->
<h1 class="page-title"><%= __('page.category') %></h1>
<!-- 遍历每个分类数据,显示分类名和数量 -->
<div class="category-lists">
<div class="tag-cloud-list is-center">
<% site.categories.data.forEach(data => { %>
<a href="<%= url_for(data.path) %>">
<!-- <span class="tags-punctuation">#</span> -->
<%= data.name %>
<span class="tagsPageCount"><%= data.length %></span>
</a>
<% }) %>
</div>
</div>

后续可以添加一个分类云,参考这里。

post 文件

tag 文件

遗留问题

  1. partial/console 文件中深色模式开关的残留问题,在刷新后网页会自动切换到深色模式
  2. partial/sidebar 文件意义不明
  3. partial/nav 文件有一部分代码意义不明
  4. partial/component/hometop/topGroup 文件今日卡片只能根据更新日期进行变化
  5. partial/component/nav/right 文件有一部分代码意义不明
  6. partial/component/nav/right 文件用到的 source/js/main.js 文件中的 percent() 函数无法成功调用语言文件的内容
  7. footer 文件的订阅部分