Hexo自定义主题(三)

标签: Hexo 分类: 前端 创建时间:2019-06-11 03:20:38 更新时间:2023-10-20 11:23:25

这篇文章是自定义主题的第三部,对于我自定义主题来说,其实没有网上其他人写的那么有条理,有步骤,我总是想到什么写什么,在我博客中其他的文章中也可以看出来,今天突发奇想,想为博客添加一些功能,就顺手写下来了。自定义主题这么久以来,对主题和整个hexo的运行来说,也多多少少的有了写经验,其实刚开始写的时候,感觉特别的复杂,查看next主题的源码,也是云里雾里的,时间长了,也就是那么回事。首先就是要学会swig语法,然后就是组织好主题的目录结构,这个模板引入了那个模板,这个模板添加了某个js文件,理清思路就好写多了。

1.添加分享

分享插件其实有很多,自己写也是一个套路,主要就是构造一个分享的url,然后执行跳转就可以了。比来比去,像百度分享,bShare分享,虽然功能强大,但是也太丑了点,我选择了iShare这个分享插件,引入也挺简单的。

引入iconfont、css和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
<link rel="stylesheet" type="text/css" href="{{ url_for(theme.lib) }}/share/style/fonts/iconfont.css">
<link rel="stylesheet" type="text/css" href="{{ url_for(theme.lib) }}/share/style/css/ishare.css">
<script src="{{ url_for(theme.lib) }}/share/iShare.js"></script>

// 添加div
<div class="iShare iShare-content"></div>

// 创建实例
<script type="text/javascript">
var iSharel=(new iShare({container:'.iShare-content',config:{
title: '这里有精彩的内容,你确定不过来看看吗?{{page.title}}:{{page.permalink}}(复制此地址在浏览器中打开)。bibichaun出品。',
description: '{{page.title}}',
url: '{{page.permalink}}',
isAbroad: false,
isTitle: true,
initialized: true,
WXoptions:{
evenType: 'click',
isTitleVisibility: true,
title: '{{page.title}}',
isTipVisibility: true,
tip: '{{page.title}}',
qrcodeW: '120',
qrcodeH: '120',
qrcodeFgc: '#4a148c',
bgcolor: '#2BAD13'
}
}}));
</script>

修改iShare

iShare自三年前完成这个插件之后,就一直没有更新了,在使用的过程中,我发现了一些问题,比如:

(1)在添加微信二维码时,脚本自动加载了当前页面下的qrcode.min.js,而不是和iShare.js同目录下的js文件,可能造成引用不到qrcode的情况,所以,我将Util.loadjs这里改掉了,在加载之前先判断iShare所在路径,然后执行加载js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//原先
Util.loadjs('qrcode.min.js', this.wx.startQR());
// 现在加载qrcode库
var js = document.scripts;
var path = "";
var c=js.length;
for(var i=0;i<c;i++){
var it=js[i];
var src=it.src;
if(src.indexOf("iShare")>=0){
path=src.substring(0,src.lastIndexOf('/')+1)
}
}
Util.loadjs(path+'qrcode.min.js', this.wx.startQR());

(2)第二个问题,是生成的微信二维码的div,作者原本是放到了body上,在窗口resize函数执行时,会不停的计算div的位置,重新放置。我觉得应该将二维码div放到相对于微信分享这个按钮的相对位置,让a包裹这个div,这样就可以去掉resize函数的监听。要做这个工作,首先修改了ishare.css,中的.iShare a,添加了position:relative,然后去掉了resize的函数监听,接着修改了setLocation函数,最后将document.body.appendChild(this.wxbox);改为了this.element.appendChild(this.wxbox);我提交了pull request,估计可能作者也没空看,要想看最新的代码,可以看我fork的bibichuan分支iShare.js

(3)第三个问题是,假如包裹分享按钮的“iShare-content”容器,刚开始是display:none,那么它就不能正确的识别箭头向上还是向下,如上图,二维码的位置始终是在微信按钮的下方。主要原因是在调用getElementTop()函数时,因为元素是隐藏的,所以获取到的offsetTop始终为零。
解决方式就是将创建和显示的位置放在show函数中,当调用显示函数的时候,也就是二维码第一次显示的时候,再创建这个二维码div,其实这样也符合软件开发的原则,如果用户不点击就不创建二维码,这样也算是节省资源。

(4)第四个问题,如果微信二维码的title和tip过长,二维码的位置就会不对。

作者在确定二维码位置时,使用的是获取二维码高度,然后定义top为负值,在操作起来,如果二维码标题太高,在setLocation函数中获取到的_boxH的offsetHeight就不是实际看到的高度,所以,我将定位top改为bottom来确定二维码的位置,详细代码参考github。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setLocation: function (flag) {
var _boxH = this.wxbox.offsetHeight,
_eW = this.element.offsetWidth,
_eH = this.element.offsetHeight,
_boxStyle = 'position:absolute; color: #000;z-index: 99999;';
_boxStyle = _boxStyle + 'left: ' + (_eW / 2 - 12) + 'px;';
if (this.upDownFlag === 'down') {
_boxStyle = _boxStyle + 'bottom: ' + (_eH) + 'px;';
} else {
_boxStyle = _boxStyle + 'top: ' + (_eH) + 'px;';
}
this.wxbox.style.cssText = _boxStyle + this.style;
flag && (this.hide());
}

最终的效果就是这样:

2.压缩和美化主题CSS、js和html

在hexo根目录下安装gulp-clean-css、gulp-uglify、gulp-htmlmin、gulp-htmlclean

1
2
npm install gulp -g
npm install gulp-clean-css gulp-uglify gulp-htmlmin gulp-htmlclean gulp --save-dev

编写gulpfile文件

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
var gulp = require('gulp');
var minifycss = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var htmlmin = require('gulp-htmlmin');
var htmlclean = require('gulp-htmlclean');
// 压缩 public 目录 css
gulp.task('minify-css', function() {
return gulp.src('./public/**/*.css')
.pipe(minifycss())
.pipe(gulp.dest('./public'));
});
// 压缩 public 目录 html
gulp.task('minify-html', function() {
return gulp.src('./public/**/*.html')
.pipe(htmlclean())
.pipe(htmlmin({
removeComments: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}))
.pipe(gulp.dest('./public'))
});
// 压缩 public/js 目录 js
gulp.task('minify-js', function() {
return gulp.src('./public/**/*.js')
.pipe(uglify())
.pipe(gulp.dest('./public'));
});
// 执行 gulp 命令时执行的任务
gulp.task('default', [
'minify-html','minify-css','minify-js'
]);

注意事项
我在使用gulp-htmlmin过程中,出现了一个奇怪的问题

找来找去,我才发现了一个基本的问题,“ <!doctype>声明必须处于HTML文档的头部,在<html>标签之前,HTML5中不区分大小写….”,这个提示确实是正确的,因为我在html中确实出现了一个<!doctype>,这个应该放到文档开头,如果非得使用这个标签,就用 &lt;!doctype&gt; 代替,这也算是html基本的知识。

3.自定义主题中的css目录

在自定义主题中编写css,如果文件夹的名字中开头是一个短杠,则hexo不主动处理其中的文件和文件夹,类似于私有的一样,除非在css根目录中某个.styl文件中使用import显示的引入其中的文件,比如在main.styl文件中引用到了_common文件夹中的文件,hexo就会主动去处理,并合并到main.css中,如果没有其他文件引入,在css/_page中的文件将不会被处理,也不会在最终生成的public文件中出现。

假如css下的某个文件夹没有短线,即使没有其他的文件引入其中的文件,在hexo也会主动处理其中的styl文件,并最终会复制到public中的css文件夹中,例如此处的p文件夹下的anime.styl文件会被处理。

最终会在public文件夹的css文件夹中生成p文件夹

同理,对于主题的模板文件layout文件夹也是如此处理。

4.图片懒加载

图片懒加载的意思,就是说图片看不见的时候,就不让他加载,等页面滚动到图片所在位置时在进行加载。主要原理是,在html的图片标签上先不对src进行赋值,通过另一个属性比如lay-src,或其他属性标志图片的地址,等页面滚动到图片所在位置时,通过js将img的src修改为图片的真实地址。在next主题中使用了hexo-lazyload-image插件,主要有两步:

1
2
1.在hexo after_post_render事件或者after_render:html事件里将生产出来的文章html代码中所有img元素都加上 data-original 属性,并把src值付给他, 然后在将src值致为loading图片
2.注入simple-lazyload脚本在每个页面最后面,当页面加载过后负责判定当前需要重新加载的图片。

安装的插件主要负责第一步操作,脚步的注入需要自己手动插入。

在我的主题中,因为我使用了layui这个组件库,所以我借助了layui提供的图片懒加载模式,当然第一步的after_post_render还是要自己手工写的,然后就是调用layui的flow插件即可。(自己写的时候,有点上当了,在主题的模板文件中,我用的是post.more渲染的文章内容,而不是post.content,所有我无论怎么改conten的内容,刷新页面都还是原样,倒腾了一下午,没找到原因,最后发现我修改了data.content,渲染文章的时候用的是data.more,所以看不出代码写的对不对),最后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//主要进行图片的处理
hexo.extend.filter.register('after_post_render', function(data){
//将图片的src属性全部替换为lay-src属性,方便进行懒加载
var root=hexo.config.root;
if(root[root.length-1]!='/'){
root=root+"/";
}
var name=hexo.theme.config.loadingImage;
if(!name||name==""){
name="loadimg.png";
}
var loadingImage=hexo.config.root+hexo.theme.config.images+"/"+name;
data.content=content.replace(/<img(\s*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
return str.replace(p2, loadingImage + '" lay-src="' + p2);
});
data.more=data.more.replace(/<img(\s*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
return str.replace(p2, loadingImage + '" lay-src="' + p2);
});

return data;
});

我发现了一个问题,就是在没有执行代码之前,懒加载的图片会变成没有图片,那种加载错误的样子,就是没有图片占位符,显示的是裂图,这里有解决方案,首先是修改<img src="loading.gif" lay-src="img.jpg">,然后修改js文件。我试了下,虽然有三种方法,但是第一种不太好,删除之后,每滚动下,就会去加载一个undefine图片,第二种就没有这个问题,即将文件lay/modules/flow.js 中的&&!e.attr(“src”)改为&&e.attr(“lay-src”),希望官方能今早解决这个占位图的问题吧。

还有一个问题就是,虽然图片是正常的实现了懒加载,但是fancybox3的图片放大问题也同时出现了,点击fancybox3时,出现的图片不能识别lay-src里面的属性,而src这个时候的值,可能还没有或者是还只是一个占位符。
这个问题的解决方式,在于fancybox3的图片加载,可以不仅仅使用src,而是可以从其包裹的a标签中修改,在img标签上添加data-original属性是重点

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
//fancybox3 显示图片代码
$('.post img')
.not('[hidden]')
.not('.group-picture img, .post-gallery img')
.each(function() {
var $image = $(this);
var imageTitle = $image.attr('title');
var $imageWrapLink = $image.parent('a');

if ($imageWrapLink.length < 1) {
var imageLink = $image.attr('data-original') ? this.getAttribute('data-original') : this.getAttribute('src');
$imageWrapLink = $image.wrap('<a data-fancybox="group" href="' + imageLink + '"></a>').parent('a');
}

$imageWrapLink.addClass('fancybox fancybox.image');
$imageWrapLink.attr('rel', 'group');

if (imageTitle) {
//$imageWrapLink.append('<p class="image-caption">' + imageTitle + '</p>');

//make sure img title tag will show correctly in fancybox
$imageWrapLink.attr('title', imageTitle);
}
});
$('.fancybox').fancybox({
backFocus:false
,hash: false
,hideScrollbar:false
});

//after_post_render代码
hexo.extend.filter.register('after_post_render', function(data){
let content=data.content;
//将图片的src属性全部替换为lay-src属性,方便进行懒加载
var root=hexo.config.root;
if(root[root.length-1]!='/'){
root=root+"/";
}
var name=hexo.theme.config.loadingImage;
if(!name||name==""){
name="loadimg.png";
}
var loadingImage=hexo.config.root+hexo.theme.config.images+"/"+name;
//添加data-original属性是重点
data.content=content.replace(/<img(\s*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
return str.replace(p2, loadingImage + '" data-original="'+p2+'" lay-src="' + p2);
});
//添加data-original属性是重点
data.more=data.more.replace(/<img(\s*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
return str.replace(p2, loadingImage + '" data-original="'+p2+'" lay-src="' + p2);
});

return data;
});

5.使用github作为自己的图床

网上有很多图床,主要的作用就是存储图片,然后发布一个链接,在自己的应用中使用图片链接。主要问题是,都有这样那样的限制,像七牛云,虽然有10G的免费空间,但是它一个月后就把测试域名回收了,必须绑定自己的已经备案的域名(要是我能备案,就这点图片,还用得着放在免费的地方吗?),我自己就吃了这个亏,最后放到七牛云上的图片,也下载不下来了。因为没有自己的域名,连下载都不让下载了。其他的一些,也都是限制数量啊,限制带宽啊,绑定域名啊,感觉有些不方便。后来我想过了,其实可以用github,直接载自己的hexo中存储图片,当作图床。步骤如下:

新建一个页面:hexo new page photo,目的就是在source下生成一个photo文件夹,然后把里面生成的index文件夹和index.md文件删除,只留下一个空的文件夹,hexo在打包生成网站的时候,会原封不动的将这个文件夹里面的内容拷贝到public目录下,通过网站地址,就可以访问到了,比如我的网站是放到github上的,就可以使用: https://bibichuan.github.io/photo/> 访问这个文件夹里面的文件了,然后把自己的图片放到这个文件夹里,在需要引用的时候,使用绝对网络路径,或者相对路径都是可以的。我这里使用的是相对路径,例如:我在 https://bibichuan.github.io/anmie 这个页面中图片地址,就可以使用”../photo/anime/0%E5%8F%B7%E5%AE%BF%E8%88%8D_1.webp”

妈妈再也不用担心我的图片存放问题了。

当然了,使用这种方式,肯定是不利于CDN加速的,如果将大量图片放到自己的图片上,也相应的会增加自己服务器的压力。但是这都没关系,反正我现在只是把网站架在了github上,先要重复利用资源。

6.将分类和标签进行首字母大写

需求主要是这样的,在我的主题中,我将文章的分类抽取出来形成了网站的菜单栏,但是有时候我会将分类写错,比如同时写了大小写不同而类型相同的两个类别,Java和java我的本意应该属于同一类,但是由于大小写的关系,它们被分成了两类,放在了不同的文件目录中。于是我突发奇想,能不能把两者合并成一种呢?比如都变成以大小字母开头的类别。刚开始我想着是使用nodejs读取文件,然后将字母进行替换,但是这有个问题,我虽然可以读取文件,但是专门处理文章的分类而不处理文章的内容,比较麻烦,而我又不会正则表达式。

(最新进展,我发现了两个好用的工具hexo-front-matter和gray-matter,可以将md头部元数据信息直接转换成对象的形式,这样就可以直接操作文件了,例子请看第八节)

然后我还尝试了在过滤器中before_post_render对文章进行修改,虽然可以将data修改为想要的类别,但是始终应用不到网页中,后来我相通了,调用before_post_render过滤器时,文章的目录其实已经生成了,这个时候在修改文章的类别,不能同时应用于文章生成后的目录上去了。

如何生成tag标签页,可以参考我的另一篇文章 Hexo自定义主题(四) 第五节

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
// 文章渲染前进行操作
hexo.extend.filter.register('before_post_render', function(data){
console.log(data);
// data.title = data.title.toLowerCase();
var categories=data.categories.data;
for(let menuItem in categories){
console.log(menuItem);
let name=categories[menuItem].name;
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
console.log(name);
categories[menuItem].name=name;
data.categories.data[menuItem].name="dd";
}
console.log(data.categories);
data.title = data.title.toLowerCase();
data.categories=categories;
return data;
});

console.log(hexo.locals);

hexo.extend.processor.register("_posts/*.md", function(file){
console.log(file);
fs.readFile(file.source,{encoding:'utf8'},function(err,files){
console.log(files)
var result = files.replace(/要替换的内容/g, '替换后的内容');
console.log(files);

fs.writeFile('./js/'+item, result, 'utf8', function (err) {
if (err) return console.log(err);
});

})
});

最后的解决方式是,用生成器去处理,在生成每一篇文章的时候,直接将其分类改掉,虽然磁盘上的文件的分类还是原先的,但是发布到网上的文章分类不会出现大小写问题造成的分类不一致的问题了。代码如下:

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
// 文章渲染前进行操作,将分类和tags标签改为首字母大写
hexo.extend.generator.register('post', function(locals){
return locals.posts.map(function(post){
// 修改分类为首字母大写
var categories=post.categories.data;
for(let menuItem in categories){
let name=categories[menuItem].name;
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
categories[menuItem].name=name;
post.categories.data[menuItem].name=name;
}
//修改标签为首字母大写
var tags=post.tags.data;
for(let menuItem in tags){
let name=tags[menuItem].name;
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
tags[menuItem].name=name;
post.tags.data[menuItem].name=name;
}

return {
path: post.path,
data: post,
layout: 'post'
};
});
});

生成标签列表时使用自定义的变量进行生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hexo.extend.filter.register('template_locals', function(locals){
//修改标签,合并同类标签,
let tags=hexo.locals.get("tags").data;
let tagsName=[];
let newTags=[];
for(let menuItem in tags){
let name=tags[menuItem].name;
//console.log(tags[menuItem]);
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
if(tagsName.indexOf(name)<0&&name!=null&&name!=""){
tagsName.push(name);
tags[menuItem].name=name;
tags[menuItem].path="/tags/"+name+"/";
newTags.push(tags[menuItem]);
}
}
locals.newTags=newTags;
return locals;
})

更新后
上面的代码,我发现了通过修改template_locals过滤器,会连草稿箱中标签也算在内,所以最新的内容请查看 Hexo自定义主题(四) 第五章。

7.奇怪的标签

我在更新代码的时候,不知道为何,输出的文章标签永远都是这几个。

我始终怀疑是vscode缓存的问题。因为我查看了多篇md文章,里面的标签明明不一样,而且,我每次更新一篇文章的时候,都会有新的标签加进来,即便是我只是在这篇文章中添加了一个空格,这篇文章也是能将这个标签加载进来的。最后的解决方法,还是我删除了整个网站,重新clone,然后重新运行了hexo s。

8.代码处理md文件

需求注意是在第六部分中提到的,将分类和标签的首字母大写,可以通过nodejs读取每一篇文章,经过处理后,再写回去,这样就实现了可以将文章的标签和分类分别处理的功能了。这里主要用到了gray-matter这个npm插件,安装方式:npm install gray-matter –save或者是cnpm install gray-matter –save,处理文章的代码如下:

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
// npm install moment --save

var rf=require("fs");
var grey_matter=require("gray-matter");
var moment=require("moment");

rf.readFile("Git和GitHub.md",'utf-8',function(err,data){
if(err){
console.log(err);
}else{
var obj=grey_matter(data);
var titleObj=obj.data;
titleObj.tags="k";
console.log(titleObj.date);
var d=moment(titleObj.date).format('YYYY-MM-DD hh:mm:ss');
titleObj.date=d;

rf.writeFile("test.md", obj.stringify(), function(error){
if(err){
console.log(error);
}else{
console.log('写文件操作成功');
}
})
}
});

安装moment,是为了处理文章的日常,grey_matter将日期读取为UTC格式,然后将其格式化后重新写入文件。这里有些小瑕疵,就是原始文件中的日期是没有单引号的

重新格式化后回写,就出现了单引号:

这不影响大局,但是对于强迫症患者,还是无法忍受的(算了,不管了,反正不影响使用)。当然,你不对日期进行处理,那么它回写回去的就是UTC时间

最后,配合hexo的处理去,就可以对每一篇文章进行处理后,原封不动的再写回去了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
hexo.extend.processor.register("_posts/*.md", function(file){
fs.readFile(file.source,{encoding:'utf8'},function(err,files){
var obj=grey_matter(files);
var titleObj=obj.data;
titleObj.tags="k";
var d=moment(titleObj.date).format('YYYY-MM-DD hh:mm:ss');
titleObj.date=d;

rf.writeFile(file.source, obj.stringify(), function(error){
if(err){
console.log(error);
}else{
console.log('写文件操作成功');
}
})

})
});
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。