动手魔改hexo-theme-icarus主题




Icarus 是一款 简单、优雅、现代化 的 Hexo 博客主题。


“最新文章”卡片 修改缩略图链接

如下图所示,recent_posts 中显示的文章列表的缩略图都是原图(文件较大),虽然每个图片大小只有几百KB而已,但是为了节省流量,我们还是选择将这里的缩略图压缩,压缩后往往只有十几KB。

STEP1 新增图片处理自定义版本

首先,我在 又拍云 新增了一个图片处理的自定义版本,功能配置中 把你认为能削减文件大小的选项都勾上。

至于间隔标识符,我延用默认的 !

这样,通过在图床图片URL结尾添加 !small,你就可以获取其压缩后的图片版本。

STEP2 魔改主题模板

进入hexo所在文件目录

打开 theme/icarus/_config.yml,放入以下配置

1
2
# 图床(啊哈呵嗨)
imghosting: 你的图床的BaseUrl

打开 themes/icarus/includes/helpers/page.js,修改辅助函数 get_thumbnail

1
2
3
4
5
6
7
8
9
hexo.extend.helper.register("get_thumbnail", function(post, version) {
const hasThumbnail = hexo.extend.helper.get("has_thumbnail").bind(this)(post);
const imgHosting = hexo.theme.config.imghosting;
const thumbnailUrl =
hasThumbnail && post.thumbnail.startsWith(imgHosting) && version
? `${post.thumbnail}${version}`
: post.thumbnail;
return this.url_for(hasThumbnail ? thumbnailUrl : "images/thumbnail.svg");
});

打开 theme/icarus/layout/widget/recent_posts.ejs,找到

1
2
3


<%= post.title %>


get_thumbnail(post) 改成 get_thumbnail(post,'!small')

STEP3 查看效果

执行 hexo clean && hexo g && hexo s 即可。

为鼠标点击添加烟花效果

打开 themes/icarus/layout/layout.ejs,在 下方添加:

1

至于 CSS 样式,我选择放入 themes/icarus/source/css/style.styl

1
2
3
4
5
6
7
/* ---------------------------------
* 鼠标点击烟花效果
* --------------------------------- */
.fireworks
position: fixed
z-index: -1
pointer-events: none

新建文件 themes/icarus/source/js/firework.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
var canvasEl = document.querySelector(".fireworks");
if (canvasEl) {
var ctx = canvasEl.getContext("2d"),
numberOfParticules = 30,
pointerX = 0,
pointerY = 0,
tap = "mousedown",
colors = ["#FF1461", "#18FF92", "#5A87FF", "#FBF38C"],
setCanvasSize = debounce(function() {
(canvasEl.width = window.innerWidth),
(canvasEl.height = window.innerHeight),
(canvasEl.style.width = window.innerWidth + "px"),
(canvasEl.style.height = window.innerHeight + "px"),
canvasEl.getContext("2d").scale(1, 1);
}, 500),
render = anime({
duration: 1 / 0,
update: function() {
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
}
});
document.addEventListener(
tap,
function(e) {
"sidebar" !== e.target.id &&
"toggle-sidebar" !== e.target.id &&
"A" !== e.target.nodeName &&
"IMG" !== e.target.nodeName &&
(render.play(), updateCoords(e), animateParticules(pointerX, pointerY));
},
!1
),
setCanvasSize(),
window.addEventListener("resize", setCanvasSize, !1);
}
function updateCoords(e) {
(pointerX = (e.clientX || e.touches[0].clientX) - canvasEl.getBoundingClientRect().left),
(pointerY = e.clientY || e.touches[0].clientY - canvasEl.getBoundingClientRect().top);
}
function setParticuleDirection(e) {
var t = (anime.random(0, 360) * Math.PI) / 180,
a = anime.random(50, 180),
n = [-1, 1][anime.random(0, 1)] * a;
return {
x: e.x + n * Math.cos(t),
y: e.y + n * Math.sin(t)
};
}
function createParticule(e, t) {
var a = {};
return (
(a.x = e),
(a.y = t),
(a.color = colors[anime.random(0, colors.length - 1)]),
(a.radius = anime.random(16, 32)),
(a.endPos = setParticuleDirection(a)),
(a.draw = function() {
ctx.beginPath(),
ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
(ctx.fillStyle = a.color),
ctx.fill();
}),
a
);
}
function createCircle(e, t) {
var a = {};
return (
(a.x = e),
(a.y = t),
(a.color = "#F00"),
(a.radius = 0.1),
(a.alpha = 0.5),
(a.lineWidth = 6),
(a.draw = function() {
(ctx.globalAlpha = a.alpha),
ctx.beginPath(),
ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
(ctx.lineWidth = a.lineWidth),
(ctx.strokeStyle = a.color),
ctx.stroke(),
(ctx.globalAlpha = 1);
}),
a
);
}
function renderParticule(e) {
for (var t = 0; t < e.animatables.length; t++) e.animatables[t].target.draw();
}
function animateParticules(e, t) {
for (var a = createCircle(e, t), n = [], i = 0; i < numberOfParticules; i++)
n.push(createParticule(e, t));
anime
.timeline()
.add({
targets: n,
x: function(e) {
return e.endPos.x;
},
y: function(e) {
return e.endPos.y;
},
radius: 0.1,
duration: anime.random(1200, 1800),
easing: "easeOutExpo",
update: renderParticule
})
.add({
targets: a,
radius: anime.random(80, 160),
lineWidth: 0,
alpha: {
value: 0,
easing: "linear",
duration: anime.random(600, 800)
},
duration: anime.random(1200, 1800),
easing: "easeOutExpo",
update: renderParticule,
offset: 0
});
}

打开 themes/icarus/layout/common/scripts.ejs,追加代码:

1
2
<%- _js(cdn('animejs', '3.0.1', 'lib/anime.min.js')) %>
<%- _js('js/firework', true) %>

这就🆗啦,赶快 素质三连( hexo clean && hexo g && hexo s ) 查看效果吧!(´▽`ʃ♡ƪ)

改造”TOC”卡片

添加 sticky 效果

🔔 BUG 待修复:在中屏自适应情况下无法 sticky

打开 themes/icarus/source/css/style.styl,找到以下样式所在位置

1
2
3
4
5
6
7
8
9
10
11
12
13
@media screen and (min-width: screen-tablet)
.column-main,
.column-left,
.column-right,
.column-right-shadow
&.is-sticky
align-self: flex-start
position: -webkit-sticky
position: sticky
top: .75rem
.column-right-shadow
&.is-sticky
top: 1.5rem

将其修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@media screen and (min-width: screen-tablet)
.column-main,
.column-left,
.column-right,
.column-right-shadow,
#toc
&.is-sticky
align-self: flex-start
position: -webkit-sticky
position: sticky
top: .75rem
.column-right-shadow
&.is-sticky
top: 1.5rem
#toc
&.is-sticky
max-height: calc(100vh - 2rem)
overflow-y: auto

打开 theme/icarus/_config.yml,对 sidebar 追加以下配置

1
2
3
4
5
6
7
8
9
10
sidebar:
# left sidebar settings
left:
sticky: false
# right sidebar settings
right:
sticky: false
# 使TOC支持sticky效果(啊哈呵嗨)
toc:
sticky: true

打开 themes/icarus/layout/widget/toc.ejs,修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<% function toc_sticky_class() {
let canTocSticky, position;
get_widgets('left').forEach(widget => {
if(widget.type === 'toc') position = widget.position;
});
get_widgets('right').forEach(widget => {
if(widget.type === 'toc') position = widget.position;
});
if(position && get_config('sidebar.' + position + '.sticky', false)) canTocSticky = false;
else canTocSticky = get_config('sidebar.toc.sticky', false);
return canTocSticky ? 'is-sticky' : '';
} %>





TOC 标题随滚动定位

新建 themes/icarus/layout/plugin/tocbot.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
<% if (get_config('toc') === true && plugin !== false && (page.layout === 'page' || page.layout === 'post')) { %>
<% if (head) { %>

<% } else { %>
<%- _js('js/reading-progress.js') %>
<%- _js(cdn('tocbot', '4.5.0', 'dist/tocbot.min.js')) %>

<% } %>
<% } %>

打开 theme/icarus/_config.yml,新增配置

1
2
3
plugins:
# tocbot(啊哈呵嗨)
tocbot: true

修改 toc.ejs,找到代码

1
<%- buildToc(_toc(post.content)) %>

将其换成

1
2
3
4
5
<% if (get_config('plugins.tocbot') !== false) {%>

<% } else { %>
<%- buildToc(_toc(post.content)) %>
<% } %>

添加 阅读进度滚动条

打开 themes/icarus/layout/widget/toc.ejs,找到代码:

1

在下方加入

1
2
3



添加样式,不用说都知道修改哪个文件了吧?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* ---------------------------------
* 阅读进度滚动条
* --------------------------------- */
.reading-progress
width: 100%;
height: 4px;
opacity: .8;
background-color: rgba(37, 117, 252, .3);
overflow: hidden

.reading-progress-bar
background-image: linear-gradient(to right,#4cbf30 0,#0f9d58 100%);
height: 4px;
width: 0;

页面浏览器标题修改

打开 themes/icarus/includes/helpers/page.js,在辅助函数 page_title 中找到以下代码片段

1
2
3
4
const siteTitle = hexo.extend.helper.get("get_config").bind(this)("title", "", true);
return [title, siteTitle]
.filter(str => typeof str !== "undefined" && str.trim() !== "")
.join(" - ");

将其修改为

1
2
3
4
5
6
const getConfig = hexo.extend.helper.get("get_config").bind(this);
const siteTitle = getConfig("title", "", true),
siteSubTitle = getConfig("subtitle", "", true);
return [title, siteTitle, siteSubTitle]
.filter(str => typeof str !== "undefined" && str.trim() !== "")
.join(" - ");

执行 hexo clean && hexo g && hexo s 即可查看效果。

百度站长自动推送工具

打开 themes/icarus/layout/common/scripts.ejs,追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
(function () {
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
script>

支持百度 MIP 和 AMP

打开 themes/icarus/layout/common/head.ejs,追加

1
2
<link rel="stylesheet" type="text/css" href="https://c.mipcdn.com/static/v2/mip.css">
<link rel="canonical" href="<%= url %>">

打开 themes/icarus/layout/common/scripts.ejs,追加

1
<script src="https://c.mipcdn.com/static/v2/mip.js">script>

打开 themes\icarus\layout\layout.ejs,给 html 标签 添加属性 mip

数学公式 和 图片展览 的使用频次实在是太少了,当你用不到的时候页面上就会加载 mathjax 和 gallery 的诸多额外 js,对网站速度还是有一定的影响的。

那么我们就修改一下配置,让 post 支持选择性启用这些插件吧!

支持 QQ 分享

题外话:

hexo 是如何通过 分割文章从而获得 excerpt 的。

https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/after_post_render/excerpt.js

打开 themes/icarus/layout/common/head.ejs,在合适的位置添加

1
2
3
<meta itemprop="name" content="<%= page_title() %>" />
<meta itemprop="description" content="<%= aha_description() %>" />
<meta itemprop="image" content="<%= page.thumbnail || get_config('url') + '/images/avatar.png' %>" />

打开 themes/icarus/includes/helpers/page.js,添加新辅助函数 aha_description

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
hexo.extend.helper.register("aha_description", function(page = null) {
page = page === null ? this.page : page;
const configDescription = hexo.extend.helper.get("get_config").bind(this)("description","");
let description = page.excerpt || page.description || configDescription;
if (description) {
description = stripHTML(description).substring(0, 200)
.trim()
.replace(/, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/\n/g, " ");
}
return description;
});

文章标题下划线动画

打开 themes/icarus/source/css/style.styl,追加

1
2
3
4
5
6
7
8
9
10
11
12
/* ---------------------------------
* 标题下划线动画
* --------------------------------- */
.title
a
background-image: linear-gradient(transparent 0%, transparent 65%,#a4c7ff 65%,#a4c7ff 90%, transparent 90%, transparent);
background-repeat: no-repeat;
background-size: 0 100%;
transition: background-size .3s cubic-bezier(0.4, 0, 1, 1);

&:hover
background-size: 100% 100%;

添加”待办事项”卡片

添加样式

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
/* ---------------------------------
* TODO卡片
* --------------------------------- */
.clearfix
overflow: auto
zoom: 1
.todo-header
border-radius: 6px 6px 0 0
background: url("/images/aha-todo-bg.jpg") no-repeat
background-size: cover
padding: 30px
position: relative
time
color: white
display: block
letter-spacing: .125rem
.day
font-size: 4rem
float: left
margin-right: 10px
.dayofweek
display: block
margin-top: 13px
font-size: 1.4rem
font-weight: 700
.add-circle
position: absolute
background: #ff3c41
color: white
border-radius: 50%
width: 40px
height: 40px
line-height: 40px
text-align: center
right: 20px
bottom: -20px
box-shadow: 0 0 13px #ff3c41
cursor: pointer
.todo-form
margin: 15px 0
width: 100%
input
width: 100%
border: 0
border-bottom: 1px solid #e6e6e6
font-family: inherit
padding: 5px 0
outline: none
&:focus
border-bottom-color: #ff3c41
.todo-content
.todo-content-title
padding: 20px
h2
font-size: 1.5rem
font-weight: normal
color: #4c4646
.task-report
font-size: 0.9rem
color: #afafaf
.todo-list
list-style: none
.task-item
padding: 15px 20px
border-top: 1px solid #f1f1f1
position: relative
&.task-done
.text
color: #ccc
.label
display: inline-block
background: #ff3c41
width: 15px
height: 15px
border-radius: 50%
float: left
margin-right: 10px
&.normal
background: #37eaa0
.text
position: relative
top: -2px
.task-action
position: absolute
right: 20px
top: 12px
z-index: 99999
.more
cursor: pointer
span
display: inline-block
margin: 0 1px
width: 6px
height: 6px
border-radius: 50%
background: #d0d0d0
.action-list
position: absolute
right: 0px
top: 23px
border: 1px solid #ccc
list-style: none
padding: 15px 10px
border-radius: 4px
background: #fff
box-shadow: 0 4px 7px rgba(0, 0, 0, 0.1)
display: none
li
padding: 3px 5px
cursor: pointer
white-space: nowrap
&:hover
background: #0ebeff
color: white
border-radius: 3px
&:focus
.action-list
display: block

向配置文件中添加配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
widgets:
- type: todo # 待办事项 Widget(啊哈呵嗨)
position: right
tasks:
About 页面大修:
done: false # 默认为false,即未完成
type: 1 # 0代表normal 1代表urgent 默认为0,即normal
TOC 滚动更新位置:
done: true
type: 0
设计 Logo SVG:
done: false
type: 0
todo 小组件大小单位用 rem:
done: false
type: 0

新建文件 themes/icarus/layout/widget/todo.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
<% const weekdayArr= new Array("星期日","星期一","星期二","星期三","星期四","星期五","星期六");
const monthArr = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
const myDate = new Date();
const dayofweek = weekdayArr[myDate.getDay()], day = myDate.getDate(), month = monthArr[myDate.getMonth()]; %>
<% const tasks = get_config_from_obj(widget, 'tasks');
let doneNum = 0, taskNum = 0;
for(const i in tasks) {
taskNum++;
if(tasks[i].hasOwnProperty('done') && tasks[i].done === true) doneNum++;
} %>



<%= day %>
<%= dayofweek %>
<%= month %>







Blog tasks


<%= doneNum %> / <%= taskNum %> tasks




    <% for (const i in tasks) { %>
    <% const hasDone = tasks[i].hasOwnProperty('done') && tasks[i].done; %>


  • <% if (hasDone) { %>
    <%= i %>
    <% } else { %>

    <%= i %>





    • Mark done

    • Edit

    • Delete



    <% } %>

  • <% } %>



首页和非首页两种 widget

首先将 配置中的 widgets 拷贝一份到 index_widgets 后自行配置。

接着把 themes/icarus/includes/helpers/layout.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
/**
* Helper functions for controlling layout.
*
* @example
* <%- get_widgets(position) %>
* <%- has_column() %>
* <%- column_count() %>
*/
module.exports = function(hexo) {
hexo.extend.helper.register("has_widget", function(type, isHome = false) {
const hasWidgets = hexo.extend.helper.get("has_config").bind(this)(
isHome ? "index_widgets" : "widgets"
);
if (!hasWidgets) {
return false;
}
const widgets = hexo.extend.helper.get("get_config").bind(this)(
isHome ? "index_widgets" : "widgets"
);
return widgets.some(widget => widget.hasOwnProperty("type") && widget.type === type);
});

hexo.extend.helper.register("get_widgets", function(position, isHome = false) {
const hasWidgets = hexo.extend.helper.get("has_config").bind(this)(
isHome ? "index_widgets" : "widgets"
);
if (!hasWidgets) {
return [];
}
const widgets = hexo.extend.helper.get("get_config").bind(this)(
isHome ? "index_widgets" : "widgets"
);
return widgets.filter(
widget => widget.hasOwnProperty("position") && widget.position === position
);
});

hexo.extend.helper.register("has_column", function(position, isHome = false) {
const getWidgets = hexo.extend.helper.get("get_widgets").bind(this);
return getWidgets(position, isHome).length > 0;
});

hexo.extend.helper.register("column_count", function(isHome = false) {
let columns = 1;
const hasColumn = hexo.extend.helper.get("has_column").bind(this);
columns += hasColumn("left", isHome) ? 1 : 0;
columns += hasColumn("right", isHome) ? 1 : 0;
return columns;
});
};

接着修改所有出现以上3个辅助函数的模板,比如 themes/icarus/layout/common/widget.ejs、themes/icarus/layout/layout.ejs,把 is_home() 用做参数即可。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×