身为一名预备程序员,对于自己使用的工具自然要有折腾的欲望。从今年七八月以来,就一直在想着如何折腾折腾Hexo. 再加上很喜欢Kunkka (其Github源已404,估计被作者自己删除了)这个Wordpress的主题,想着把它移植到Hexo上来。就在这里记载一下碰到的问题和解决方案吧。
UPDATE 2016/10/28: 修改了部分用词
UPDATE 2017/9/22: 更新代码
UPDATE 2018/6/21: 再度更新代码
主题的创建 方便起见,我在主题中插入了jQuery 和FontAwesome . 实际上,没有这两样我浑身难受。
主题模板引擎 Hexo主要提供了三款模板引擎,分别是EJS , Jade (现名Pug ), Swig .
我个人看来,Jade代码结构优雅(和Python一样,不优雅不让跑),但是代码结构和HTML文档相去甚远,非常不直观。这也就意味着,要使用它不光要学习HTML, 还得特别地去学习Jade语法。而且据我本人少量的测试而言,Jade的主题和EJS的主题相比生成速度慢了几倍。EJS duang的一下就生成完了,Jade你还能在终端里看着它慢慢地一行一行的输出生成了xxx文件。
而EJS和Swig两个相比,在我使用的功能上我感觉不出什么差别。两个都和世界上最好的语言一样,动态生成的部分穿插在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 ($ ) { var upperLimit = 500 ;var scrollElem = $('#totop' );var scrollSpeed = 500 ;$(window ).scroll(function ( ) { if ( $(document ).scrollTop() > upperLimit ) { $(scrollElem).addClass("display" ); }else { $(scrollElem).removeClass("display" ); } }); $(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" ; 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>
内。
我给目录定下了几个样式上的目标:
跟随页面滚动
当滚动到某个标题时,目录的对应链接要被高亮。
跟随页面滚动这个比较好做,保持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 ]; }), lastId = null , active = $(); $(window ).scroll(function ( ) { var fromTop = $(this ).scrollTop() + offset; 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; if ($(this ).height() != link.height()) return ; if (thisTop <= 0 ) toc.scrollTop(tocTop + thisTop); 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; border-left : 1px solid transparent; &: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;}.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 ; &: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;} } }
首先我们得生成我们需要的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里绝对不好看,也不合理。因此,我选择干掉它。
其实上面的样式和原来的相比,差了width
和margin-left
两个,这两个我准备用js来动态调整。由于脚注文字有多有少,直接统一一个很大的width
对于文字少的来说有一大片空白,相当不好看,而且在手机上会溢出屏幕。而margin-left
应该为width
一半的负值,来保证tooltip处于脚注链接的正上方。如果改了width
的话,margin-left
也要跟着改变。
其实对于文字少的来说,外层span
的width
不会变,但内层span
的width
是会跟着文字量改变的。据此我们不用自己计算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 ); 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_layout
是String
或String
的Array
, 代表该页面使用的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" ){ } }); });
Hexo插件使用 hexo-generator-search 一个基于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 ) { 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 ; } 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 ; 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; } } }); } 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 ) { 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); 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装饰这类的细节工作了。