Please enable JavaScript to view the comments powered by Disqus.

Hexo折腾笔记

身为一名预备程序员,对于自己使用的工具自然要有折腾的欲望。从今年[1]七八月以来,就一直在想着如何折腾折腾Hexo. 再加上很喜欢Kunkka(其Github源已404,估计被作者自己删除了)这个Wordpress的主题,想着把它移植到Hexo上来。就在这里记载一下碰到的问题和解决方案吧。

UPDATE 2016/10/28: 修改了部分用词

UPDATE 2017/9/22: 更新代码

UPDATE 2018/6/21: 再度更新代码

主题的创建

方便起见,我在主题中插入了jQueryFontAwesome. 实际上,没有这两样我浑身难受。

主题模板引擎

Hexo主要提供了三款模板引擎,分别是EJS, Jade(现名Pug), Swig.

我个人看来,Jade代码结构优雅(和Python一样,不优雅不让跑),但是代码结构和HTML文档相去甚远,非常不直观。这也就意味着,要使用它不光要学习HTML, 还得特别地去学习Jade语法。而且据我本人少量的测试而言,Jade的主题和EJS的主题相比生成速度慢了几倍。EJS duang的一下就生成完了,Jade你还能在终端里看着它慢慢地一行一行的输出生成了xxx文件。

而EJS和Swig两个相比,在我使用的功能上我感觉不出什么差别。两个都和世界上最好的语言[2]一样,动态生成的部分穿插在HTML当中。不过,Swig在npm上已经没人维护了。

我本人使用的是EJS, 它脚本部分全盘使用Javascript语法,生成快,学起来也快,而且在之前有改进(xiā gǎi)Anisina的经验(它是用EJS写的)。最最重要的是,我使用的文本编辑器Kate有它的高亮文件。这一点非常非常非常重要。

主题CSS预处理器

Hexo 所支持的CSS预处理器有Sass, Stylus, Less三款。另两个我没用过,不过Stylus功能绝对够多够强,够用了。

你要是不嫌麻烦,也可以不使用预处理器,直接莽原生CSS也是可以的。

主题结构

这些在Hexo的文档(Theme, Template)里都有描写,虽然不是很详细,但也够用了。

Hexo主题相关

“回到顶部”按钮

按钮样式

刚开始我是仿自Maupassant使用一个小按钮来实现“回到顶部”,而且当滚动到一定位置时才出现。

这一个按钮由以下部分组成:

totop.ejs
1
2
3
<div id="totop" class>
<button class="button button-square button-small" title=<%- __('to_top')%>><i class="fa fa-chevron-up"></i></button>
</div>

button, button-square, button-small是我引自BUTTONS的按钮样式(感谢@maokwen的推荐),你也可以使用其他的按钮样式。

totop.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#totop {
position: fixed;
bottom: 50px;
right: 50px;
display: block;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 0.2s, opacity 0.2s linear;
-moz-transition: visibility 0.2s, opacity 0.2s linear;
transition: visibility 0.2s, opacity 0.2s linear;
}
#totop.display {
visibility: visible;
opacity: 1;
}
totop.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
(function($) {
// When to show the scroll link
// higher number = scroll link appears further down the page
var upperLimit = 500;

// Our scroll link element
var scrollElem = $('#totop');

// Scroll to top speed
var scrollSpeed = 500;

// Show and hide the scroll to top link based on scroll position
$(window).scroll(function () {
if ( $(document).scrollTop() > upperLimit ) {
$(scrollElem).addClass("display"); // fade back in
}else{
$(scrollElem).removeClass("display"); // fade out
}
});

// Scroll to top animation on click
$(scrollElem).click(function(){
$('html, body').animate({scrollTop:0}, scrollSpeed); return false;
});
})(jQuery);

将其插入到合适的地方即可。

百分比样式

后来看见了lijiancheng0614使用的百分比样式,我感觉它更强,于是换用了这个。这个按钮是一个圆形按钮,并且有一个实时更新的圆形进度条显示你当前阅读进度。虽然功能更强,但代码量更大,性能损耗更多。具体如何取舍看你自己了。

以下是我现在正在使用的代码,你应该能从右下角的那个圆圈感受效果如何。

totop.ejs
1
2
3
4
<div id="totop" title="<%= __('to_top') %>">
<canvas id="totop-canvas" width="48" height="48"></canvas>
<div id="totop-percent"></div>
</div>
totop.styl
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
#totop {
background-color: #eee;
border-radius: 100%;
bottom: 5%;
height: 48px;
width: 48px;
position: fixed;
right: -100px;
z-index: 99;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
transition: 0.5s;
&.display {right: 10px;}
}

#totop-percent {
font-size: 16px;
height: 48px;
line-height: 48px;
position: absolute;
text-align: center;
top: 0;
width: 48px;
color: #555;
cursor: pointer;
&:before {content:attr(data-percent);}
&:hover:before {
display: inline-block;
font: 14px/1em FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
content: "\f176";
}
}

我们在触发hover的时候将内容改成FontAwesome的上箭头,记得把字体正确引用进来。正常时候就显示数字百分比。

totop.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
$(function() {
var totop = $("#totop"),
canvas = $("#totop-canvas"),
percent = $("#totop-percent"),
width = canvas.width(),
height = canvas.height(),
center = width / 2,
radius = parseInt((width - 3) / 2),
ctx = canvas[0].getContext("2d");

function drawCircle(color, percent) {
ctx.beginPath();
ctx.arc(center, center, radius, - Math.PI / 2, Math.PI * 1.5 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = "round"; // butt, round or square
ctx.lineWidth = 3;
ctx.stroke();
}

totop.click(function() {
$("body, html").animate({
scrollTop: 0
}, 800);
});

$(window).scroll(function() {
var docHeight = $(document).height() - $(window).height(),
scrollTop = $(window).scrollTop(),
per = parseInt(scrollTop / docHeight * 100);
if (scrollTop >= 200) {
totop.addClass("display");
ctx.clearRect(0, 0, width, height);
drawCircle("#efefef", 1);
drawCircle("#555555", per/100);
} else
totop.removeClass("display");
percent.attr("data-percent", per);
});
});

将其插入到合适的地方即可。这段代码有报告说无法正常运作的,也有说之前的无法运作这个完好的,很神秘。

目录样式

首先,来探索Hexo自带helpertoc()生成的目录的HTML(该部分目录摘自我的博客二叉树的遍历-栈):

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
<ol class="toc">
<li class="toc-item toc-level-2">
<a class="toc-link" href="#直接遍历算法"><span class="toc-text">直接遍历算法</span></a>
<ol class="toc-child ">
<li class="toc-item toc-level-3">
<a class="toc-link" href="#有关约定"><span class="toc-text">有关约定</span></a>
</li>
<li class="toc-item toc-level-3">
<a class="toc-link" href="#使用系统栈"><span class="toc-text">使用系统栈</span></a>
<ol class="toc-child ">
<li class="toc-item toc-level-4">
<a class="toc-link" href="#前序遍历"><span class="toc-text">前序遍历</span></a>
</li>
<li class="toc-item toc-level-4">
<a class="toc-link" href="#中序遍历"><span class="toc-text">中序遍历</span></a>
</li>
<li class="toc-item toc-level-4">
<a class="toc-link" href="#后序遍历"><span class="toc-text">后序遍历</span></a>
</li>
</ol>
</li>
<li class="toc-item toc-level-3">
<a class="toc-link" href="#使用自定义栈"><span class="toc-text">使用自定义栈</span></a>
<ol class="toc-child ">
<li class="toc-item toc-level-4">
<a class="toc-link" href="#前序遍历-v2"><span class="toc-text">前序遍历</span></a>
</li>
<li class="toc-item toc-level-4">
<a class="toc-link" href="#中序遍历-v2"><span class="toc-text">中序遍历</span></a>
</li>
<li class="toc-item toc-level-4">
<a class="toc-link" href="#后序遍历-v2"><span class="toc-text">后序遍历</span></a>
<ol class="toc-child">
<li class="toc-item toc-level-5">
<a class="toc-link" href="#仙术"><span class="toc-text">仙术</span></a>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>

分层非常明显。每个链接的目的地就是对应的heading, heading的id就是链接地址去掉#. 各个markdown渲染器生成的结果虽然会有些许不同,但链接和id一定是对应的(Hexo的toc()就是根据heading的id来生成目录的,不对应的话要么没有目录,要么链接目的地是undefined)。

除此之外,还可以给目录做个外层包装。在本主题中,目录被包裹在<div id="toc"></div>内。

我给目录定下了几个样式上的目标:

  1. 跟随页面滚动

  2. 当滚动到某个标题时,目录的对应链接要被高亮。

跟随页面滚动这个比较好做,保持position: fixed然后设置好left/right, top/bottom就好。但是仍然会有些许问题,比如滚动到文章底部时目录还能继续向下跟随移动,而且top无法自适应变化(在本主题中,导航栏高55px, 目录希望距离顶部35px因此top的初值是90px. 而90px的top滚动到文章区时无疑太大了)。像这样动态设置位置,只能依赖脚本。

1
2
3
4
5
6
7
8
9
10
$(document).ready(function(){
var toc = $("#toc"), post = $(".post-body");
var post_height = post.position().top + post.outerHeight(),
toc_height = toc.height(),
pos_max = post_height - toc_height;
$(window).scroll(function(){
var scroll_top = $(window).scrollTop();
toc.css("top", scroll_top<55? 90-scroll_top: (pos_max>scroll_top? 35: pos_max-scroll_top));
});
});

其中,post-body是包裹文章的div, post_height就是文章区域底部的位置。使用这段脚本,就可以解决问题1了。

问题2的解决就比较麻烦了,好在有别的插件可用。经过我一番搜寻,实现了类似于Bootstrap里scrollspy()类似功能的轮子不少,但是没几个能用的(也许是我不会用?)。在Github上面的那几个(r3plica/Scrollspy, thesmart/jquery-scrollspy, softwarespot/jquery-scrollspy, sxalexander/jquery-scrollspy)都没有起效;而jgallen23/toc虽然有点小bug但也能用,不过最大的问题是,它的目录是附送的代码动态生成的,各个HTML的class都和我现在的目录样式的class不一样,我要是配合它还得改css. 出于一种懒人的心态,我没有去用它。

最终,我还是屈服于Bootstrap的淫威之下。在代码里插入Bootstrap的scrollspy.js, 然后写这么一段脚本:该自己造轮子了,pong友!下面是我自己做的简单scrollspy,不考虑性能,不考虑可扩展,简单粗暴:

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
function scrollSpy(menuSelector, options) {
var menu = $(menuSelector);
if(!visible(menu))
return;
options = options || {};
var offset = options.offset || 0;
var activeClassName = options.activeClassName || "active";

var scollTarget = menu.find("a").map(function() {
var item = $($(this).attr("href"));
if (item.length)
return item[0]; // avoid becoming 2-dim jquery array
}), lastId = null, active = $();

$(window).scroll(function() {
// Get container scroll position
var fromTop = $(this).scrollTop() + offset;

// Get id of current scroll item
var id = scollTarget.filter(function() {
return $(this).offset().top < fromTop;
}).last().attr("id") || "";

if (lastId !== id) {
active.removeClass(activeClassName);
var newActive = [];

for(var target = menu.find("[href='#" + id + "']");
target.length && !target.is(menu);
target = target.parent()) {
if(target.is("li"))
newActive.push(target[0]);
}
active = $(newActive).addClass(activeClassName).trigger("scrollspy");
lastId = id;
}
});
}

这个scrollspy添加了一个自定义jQuery事件scrollspy,需要传入的是你的菜单的选择器,一个<ol>或者<ul>。有了scrollspy就能开始搞事了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$(document).ready(function(){
var tocContainer = $("#toc");
var toc = tocContainer.children(), tocHeight = toc.height();
scrollSpy(tocContainer, {offset: 200});

$(".toc-item").on("scrollspy", function() {
var tocTop = toc.scrollTop(),
link = $(this).children(".toc-link"),
thisTop = link.position().top;
// make sure the highlighted element contains no child
if($(this).height() != link.height())
return;
// if the highlighted element is above current view of toc
if(thisTop <= 0)
toc.scrollTop(tocTop + thisTop);
// else if below current view of toc
else if(tocHeight <= thisTop)
toc.scrollTop(tocTop + thisTop + link.outerHeight() - tocHeight);
});
});

添加nav这个class, 是因为目前的Bootstrap使用scrollspy()的时候要求目标有nav这个class, 在开发中的v4版Bootstrap就没有这个要求了。使用这个之后,滚动到对应标题,目录里的链接以及它的上级标题的链接会添加一个active的class, 退出这段区域时删除class. 可以据此设置高亮。

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
#toc {
position: fixed;
right: 5%;
width: 20%;
height: 90%;
top: 90px;
}

for i in (1..6) {
.toc-level-{i} .toc-text {margin-left: i em;}
}

.toc-text {white-space: nowrap;}

.toc-link {
color: #999;
display: inline-block; // stretch to parent height
border-left: 1px solid transparent; // avoid "jumping" on hover
&:hover {text-decoration: none;}
}

.toc {
padding: 0;
margin: 0;
line-height: 1.8em;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
height: 100%;
}

.toc-child {
margin-left: 0;
padding-left: 0;
}

.toc-item {
list-style-type: none;
&:hover > .toc-link {border-left: 1px solid $color-theme;}
&.active {
> .toc-link {
color: $color-theme;
border-left: 2px solid $color-theme;
}
if(hexo-config('toc.collapse')) { .toc-child {display: block;} }
}
}

我是仿照Bootstrap官网的目录做的样式。要注意层级缩进的时候要使用margin-left, 如果使用padding-left那么被高亮时左边边框无法对齐。$color-theme填一个你喜欢的颜色即可。

如果还想和Bootstrap的更像,只显示当前视图范围内的刺激标题,其余的收拢在最高级标题里,那么再加上这段css:

1
2
.toc-child { display: none; }
.toc-item.active .toc-child { display: block; }

大功告成。

脚注样式

由于默认渲染器hexo-renderer-marked不支持footnote语法,所以为了使用脚注,必须将markdown渲染器换到hexo-renderer-markdown-it, 并开启markdown-it插件。

其使用方式就是

1
2
Test [^1]
[^1]: Footnote

生成对应的html文档是

1
2
3
4
5
6
7
8
9
10
11
12
13
<p>Test
<sup class="footnote-ref">
<a href="#fn1" id="fnref1">[1]</a>
</sup>
</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item">
<p>Footnote <a href="#fnref1" class="footnote-backref"></a></p>
</li>
</ol>
</section>

然而这样的话,要看脚注就要跳来跳去地看,比较影响正常的阅读。在我在网上苦苦寻求了一阵之后,我看到了一个很酷炫的tooltip样式(你们可以上Demo来感受一下),因此我决定把脚注改成这个样子的。

既然都是这种样式的了那后面的脚注内容(分割线footnotes-sep和脚注内容footnotes)都不需要了。首先上样式表:

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
.footnotes-sep, .footnotes {display: none;}

/* Style from https://github.com/codrops/TooltipStylesInspiration */
.footnote-ref {
vertical-align: super;
font-size: smaller;
display: inline;
position: relative;
&.active {
a:after { pointer-events: auto;}
.fn-content {
pointer-events: auto;
opacity: 1;
-webkit-transform: translate3d(0,0,0) rotate3d(0,0,0,0);
transform: translate3d(0,0,0) rotate3d(0,0,0,0);
}
}
> a {
cursor: pointer;
display: inline-block;
font-weight: 700;
&:after {
content: '';
position: absolute;
width: 360px;
height: 20px;
bottom: 100%;
left: 50%;
pointer-events: none;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
}
}

.fn-content {
position: absolute;
display: inline-block;
z-index: 14;
left: 50%;
margin: 0 0 20px;
bottom: 100%;
line-height: 1.4;
box-shadow: -5px -5px 15px rgba(48,54,61,0.2);
border-radius: 0.5em;
background: #2a3035;
opacity: 0;
pointer-events: none;
-webkit-transform: translate3d(0,-10px,0);
transform: translate3d(0,-10px,0);
-webkit-transition: opacity 0.3s, -webkit-transform 0.3s;
transition: opacity 0.3s, transform 0.3s;
/* Arrow */
&:after {
content: '';
top: 100%;
left: 50%;
border: solid transparent;
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: transparent;
border-top-color: #2a3035;
border-width: 10px;
margin-left: -10px;
}
.fn-text {
line-height: 1.35;
display: inline-block;
padding: 1.31em 1.21em 0;
font-size: 14.5px;
color: #fff;
z-index: 8;
a {font-weight: bold;}
/* p {margin-bottom: 0;}*/
}
}

首先我们得生成我们需要的html, 改markdown渲染器代码虽然一劳永逸,但太麻烦了,我们可以使用jQuery来生成tooltip的代码:

1
2
3
4
5
6
7
8
9
10
$(document).ready(function(){
$(".footnote-ref").each(function(){
var id = $(this).children("a").attr("href").substr(1),
footnote = $(document.getElementById(id)),
outer_wrapper = $("<span>",{"class":"fn-content"}),
inner_wrapper = $("<span>",{"class":"fn-text"});
footnote.find(".footnote-backref").remove();
$(this).append(outer_wrapper.append(inner_wrapper.html(footnote.html())));
});
});

footnote-backref是脚注部分返回的小图标,放在脚注tooltip里绝对不好看,也不合理。因此,我选择干掉它。

其实上面的样式和原来的相比,差了widthmargin-left两个,这两个我准备用js来动态调整。由于脚注文字有多有少,直接统一一个很大的width对于文字少的来说有一大片空白,相当不好看,而且在手机上会溢出屏幕。而margin-left应该为width一半的负值,来保证tooltip处于脚注链接的正上方。如果改了width的话,margin-left也要跟着改变。

其实对于文字少的来说,外层spanwidth不会变,但内层spanwidth是会跟着文字量改变的。据此我们不用自己计算width, 等浏览器绘制完毕直接把这个值拿来用就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$(document).ready(function(){
var position = function(){
var content = $(".fn-content").removeAttr("style");
if($(window).width() < 640)
content.css("width",$(window).width()/2);
else
content.css("width",340); // default value
content.each(function(){
var width = $(this).children(".fn-text").outerWidth();
$(this).css({
"width": width,
"margin-left": width/-2
});
});
}
position();
$(window).resize(position());
});

因为要根据屏幕大小确定width初始值,所以这个函数不光在document加载完毕后要执行,window改变大小的时候也要跟着执行。

解决完了样式的事情我们再来解决显示的事情。我希望的是点击脚注链接它要显示,再次点击时消失,点击别处也要消失(包括点击其他的脚注链接)。因此不能用$(".footnote-ref").click()来实现,而要通过整个document的点击事件来实现。这个点击的判断我们可以直接摘抄Stack Overflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(document).ready(function(){
$(document).click(function(t){
var target = $(".fn-content"),
clicked = $(t.target);
if(target.is(clicked) || target.has(clicked).length != 0)
t.stopPropagation();
else {
var parent = clicked.parents(".footnote-ref"),
active = $(".footnote-ref.active");
if(!active.is(parent))
active.removeClass("active");
if(!parent.length){
parent.hasClass(active)?parent.removeClass("active"):parent.addClass("active");
}
}
});
});

将以上几段js合体(假设你放到的是js/footnote.js),插入到有脚注的页面即可。可以使用这个简单的Hexo插件来实现,也可以简单粗暴地在所有页面都插入:

1
2
3
4
5
6
7
8
hexo.extend.filter.register('after_post_render',function(data){

if(data.content.indexOf("#fn") != -1){
data.content += "<script src=\"js/footnote.js\" type=\"text/javascript\"></script>";
}

return data;
});

其实还有别的脚注tooltip化方案,比如bigfoot.js或者是这篇博客介绍的方案,但是前者体积太大。后者我看着感觉不错。

在js插件中访问设置

使用hexo.config来访问站点设置,hexo.theme.config来访问主题设置。

根据设置生成CSS

在Stylus里使用hexo-config(entry)能获取主题设置里的entry条目内容。可以使用这个功能根据设置生成不同的CSS, 而不用预先给你的<div>设置上好几个class来调整外观。

hexo-config的定义在hexo-renderer-stylus里如下:

https://github.com/hexojs/hexo-renderer-stylus/blob/master/lib/renderer.js
40
41
42
43
44
function defineConfig(style) {
style.define('hexo-config', function(data) {
return getProperty(self.theme.config, data.val);
});
}

自定义页面生成

将下面的js放到主题文件夹下的scripts文件夹里,文件名随意。

1
2
3
4
5
6
7
8
9
"use strict";

hexo.extend.generator.register(some_name,function(locals){
return {
path: page_path,
data: {},
layout: page_layout
};
});

细节解释如下:

  • some_name是一个String, 原则上这个名字可以随便填。但是Hexo自己已经占用了asset, page, post这三个,所以除此之外都可以。
  • data代表除页面模板之外的页面内容。并非字符串
  • page_path是一个String, 内容应该是该网页相对根的路径,不包含代表根目录的/.
  • page_layoutStringStringArray, 代表该页面使用的layout.

示例,比如我要想生成404页面(已经写好了404.ejs

1
2
3
4
5
6
7
hexo.extend.generator.register('_404',function(locals){
return {
path: '404.html',
data: {},
layout: ['404','layout']
};
});

这样Hexo就会根据layout/404.ejs的模板在网站根目录下生成404.html. 如果找不到404.ejs的话,就会转而使用layout.ejs模板。

还可以同时生成多个页面:

1
2
3
4
5
6
7
hexo.extend.generator.register(some_name,function(locals){
return [
{path: page_path1,data: {},layout: page_layout1},
{path: page_path2,data: {},layout: page_layout2},
{path: page_path3,data: {},layout: page_layout3}
];
});

这样做的好处就是,举例来说,不需要用户自己去写一个404.md来使用404页面,自动就能生成。

选择性生成css或js

比如,我在主题的source/js文件夹下放了duoshuo.min.js这个多说本地化脚本,当设置里没启用多说的时候这个js文件还是会跟着生成,跟着发布到你的博客服务器上,浪费流量。该怎么办呢?

把如下js放进主题的scripts文件夹下(文件名随意):

1
2
3
4
5
6
7
8
9
'use strict';

hexo.extend.filter.register('after_generate',function(){
var duoshuo = hexo.theme.config.duoshuo_shortname;

if(!duoshuo || duoshuo.length == 0){
hexo.route.remove('js/duoshuo.min.js');
}
});

其中duoshuo_shortname是主题设置里的一个条目。

这样,当duoshuo_shortname为空时,duoshuo.min.js不会跟着生成,从而减少所需加载量。

HTML分块

你可以将经常变动,而使用又非常频繁的部分单独拿出来,使用上面提到的技巧生成一个单独的页面,使用jQuery来加载它。

以我的主题为例,“最近文章”这个插件属于侧边栏,而侧边栏几乎在每个页面里面都有。每当post有哪怕一个很小的改动,“最近文章”就需要更新,导致侧边栏内容需要重新生成,进而导致所有的页面全部重新生成。将它单独拿出来,就可以避免这一情况发生。

首先,生成一个单独的页面/components/recent-posts.html.

recent_posts.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
layout: none
---
<% if (site.posts.length){ %>
<h3><%= __('recent_posts') %></h3>
<ul class="list">
<% site.posts.sort('updated', -1).limit(8).each(function(post){ %>
<li>
<p><a href="<%- url_for(post.path) %>" class="tooltipped tooltipped-n" aria-label="<%= post.title || __('untitled') %>"><%= truncate(post.title || __('untitled'),{length: 23,omission:"..."}) %></a></p>
<p><%= __('updated') + __(':') %>
<span class="update-time"><%= post.updated.format('YYYY-MM-D h:m a') %></span></p>
</li>
<% }) %>
</ul>
<% } %>

里面的layout: none非常关键,因为我们只想要这一个部分而不是一个完整的网页。

此时插入这个插件就不能使用<%- partial('_widget/recent_posts') %>来插入,而是使用:

1
2
3
4
5
<!-- Something before -->
<% if (site.posts.length){ %>
<div class="widget recent-posts"></div>
<% } %>
<!-- Something after -->

并且插入这样一段脚本:

1
2
3
$(document).ready(function(){
$(".widget.recent_posts").load("/components/recent-posts.html");
});

如果还想对html做点什么修改的话,改成:

1
2
3
4
5
6
7
$(document).ready(function(){
$(".widget.recent_posts").load("/components/recent-posts.html",function(response,status){
if(status === "success"){
// something
}
});
});

Hexo插件使用

一个基于jQuery的本地搜索插件。它本身不提供搜索功能,它所做的只是生成博客内所有页面的索引。该插件提供的搜索内容相当于Hexo生成阶段的post.content. 在作者博客那边的代码里有个小错误,它会导致搜索返回的匹配字符串异常地长。

既然我都把这个单独拿出来了那肯定就要做一点自己的改进了。作者提供的代码是直接去掉所有html tag再搜索,但有一点缺陷,就是存放在<script><style>里的内容也会被算进搜索范围内,还有就是代码高亮的行号也会被算在内。针对这一点,我们在搜索之前利用jQuery干掉这些内容:

默认情况下,Hexo的行号是静态生成好的,而且被<td class="gutter">包着。删去$(".gutter")就可以了。

1
2
3
4
5
6
function stripper(content){
var wrapper = $("<div>"+ content +"</div>");
wrapper.find("script,style").remove();
$(".gutter",wrapper).remove();
return wrapper.html();
}

剩下的搜索代码,可以使用作者提供的:

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
var searchFunc = function(path, search_id, content_id) {
'use strict';
$.ajax({
url: path,
dataType: "xml",
success: function( xmlResponse ) {
// get the contents from search data
var datas = $( "entry", xmlResponse ).map(function() {
return {
title: $( "title", this ).text(),
content: stripper($("content",this).text()),
url: $( "url" , this).text()
};
}).get();
var $input = document.getElementById(search_id);
var $resultContent = document.getElementById(content_id);
$input.addEventListener('input', function(){
var str='<ul class=\"search-result-list\">';
var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
$resultContent.innerHTML = "";
if (this.value.trim().length <= 0) {
return;
}
// perform local searching
datas.forEach(function(data) {
var isMatch = true;
var content_index = [];
var data_title = data.title.trim().toLowerCase();
var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
var data_url = data.url;
var index_title = -1;
var index_content = -1;
var first_occur = -1;
// only match artiles with not empty titles and contents
if(data_title != '' && data_content != '') {
keywords.forEach(function(keyword, i) {
index_title = data_title.indexOf(keyword);
index_content = data_content.indexOf(keyword);
if( index_title < 0 && index_content < 0 ){
isMatch = false;
} else {
if (index_content < 0) {
index_content = 0;
}
if (i == 0) {
first_occur = index_content;
}
}
});
}
// show search results
if (isMatch) {
str += "<li><a href='"+ data_url +"' class='search-result-title'>"+ data_title +"</a>";
var content = data.content.trim().replace(/<[^>]+>/g,"");
if (first_occur >= 0) {
// cut out 100 characters
var start = first_occur - 20;
var end = first_occur + 80;
if(start < 0){
start = 0;
}
if(start == 0){
end = 100;
}
if(end > content.length){
end = content.length;
}
var match_content = content.substring(start, end);
// highlight all keywords
keywords.forEach(function(keyword){
var regS = new RegExp(keyword, "gi");
match_content = match_content.replace(regS, "<em class=\"search-keyword\">"+keyword+"</em>");
});

str += "<p class=\"search-result\">" + match_content +"...</p>"
}
str += "</li>";
}
});
str += "</ul>";
$resultContent.innerHTML = str;
});
}
});
}

如果你的搜索框插入的是

1
2
3
4
<div id="site_search">
<input type="text" id="local-search-input" name="q" results="0" placeholder="search my blog..." class="form-control"/>
<div id="local-search-result"></div>
</div>

那么还要在页面当中插入

1
2
3
4
5
6
7
8
<script type="text/javascript">
var search_path = "<%= config.search.path %>";
if (search_path.length == 0) {
search_path = "search.xml";
}
var path = "<%= config.root %>" + search_path;
searchFunc(path, 'local-search-input', 'local-search-result');
</script>

剩下就是一些css装饰这类的细节工作了。


  1. 2016年

  2. PHP

作者:Dr. A. Clef
发布日期:2016-09-10
修改日期:2018-06-22
发布协议: BY-SA