“如白驹过隙,转瞬即逝。如山岳层峦,连绵不绝。”
博客优化:Github Page 博客添加搜索功能
前言
闲里偷忙瞧瞧给博客整了个搜索功能
这两天下雪了 ❄️,真好
南方孩子第一次看到积雪,激动坏了 ⛄
Github Page 简介
GitHub Pages是 Github 推出的一个静态网站托管服务,它可以从仓库获取 HTML、CSS 和 JavaScript 文件,并解析成一个网站,通过特定的域名进行访问(大多为xxx.github.io
),分为个人、组织、项目三种。
所以其实很多开源项目的首页都是通过 Github Page 来实现的,比如著名开源组织Square Open Source的网站(OKHttp就是他们整的)
Github Page 规定一个账户只能创建一个 个人或组织站点,项目站点则没有限制。简单来说,我们可以把 Github Page 当成一个服务器,自然可以搭建博客。如果你也想通过 Github Page 搭建个人博客,可以参考qiubaiying的这篇文章(Readme.md)。最初我也是通过这篇文章搭建的博客,在此感谢 BY 大佬。
虽然 BY 大佬为我们提供了模板,但是其中并没有搜索功能。一开始我的文章比较少还无所谓,但是随着文章的变多,寻找一些历史文章就必须通过首页-> Tag -> 文章
来进行查找,比较费时费力,特别是我还有一些记录 Git 命令或操作流程的文章,经常需要用到(想不起来就得来看看自己小抄嘛)。
于是乎,就想着来写一个搜索的功能啦,不过最初我只是有这个想法,不知道如何入手,直到看到了Weiwq 的博客,在这里感谢 Weiwq 大佬。(ps. Weiwq 的博客中还有许多有趣小功能,如置顶、中英文转换等)
Github Page 作为个人博客的优缺点
既然提到了就顺便说一下优缺点吧,这玩意我老早就想吐槽了=。=
优点比较简单,就是咱不用自己租服务器,所以基本上是白嫖,白嫖谁不爱嘛。而且博客模板(主题)也有许多开源的代码实现了,可以找找自己喜欢的样式(当然 Hexo 等也自带许多主题,有些高大上的还得收费呢)
缺点说实话就有点多了,因为用的是 Github 的服务器,再加上“网络长城”的限制,导致访问不是很稳定,常会有图裂的情况;其次就是前不久 Github Page 对其使用作出了限制,包括但不限于以下几点:
- GitHub Pages 的存储仓库
建议
不超过 1GB(软限制,Github 对每个仓库都有这个建议,毕竟太大克隆都要下载半天) - GitHub Pages 的发布站点不能超过 1GB(硬限制,但是我要是能写文章超过 1GB,估计那会儿头发也掉光了)
- 带宽限制每月 100GB(软限制)
- 限制站点每小时 10 次生成(软限制)
- 在高并发或流量高峰期会限制速率
具体内容可见Github Pages 使用限制
但是要我说,还是钱钱重要,什么流量容量限制,对我这种没几篇文章,文章也没几个字的小众人物来说跟没有一样嘛。钱钱 💰,我的钱钱 💰
实现搜索功能
如果我的 gif 能正常加载的话,咱们的一个简单的搜索功能大概是下面这样:
导航栏的右上角多了个搜索图标,点击后弹出一个搜索页面;在搜索框中输入会动态检索匹配的文章,并允许我们点击跳转。
假设我们是 BY 大佬的同门弟子,那么现在我们的博客就是通过 Jekyll 来生成的。Hexo 大家可能听的会比较多,Jekyll 和它差不多,都是静态网页的生成器,想要进一步了解的可以移步Jekyll 官网。
这里先让大家知道有这么个东西,除了我们熟悉的三剑客外,等会还得靠它实现搜索功能呢。
实现静态布局
然后就正式开始实现搜索功能啦,当然先从简单的布局入手
首先我们在导航栏加一个搜索的 icon:
<!-- nav.html -->
······
<!-- Collect the nav links, forms, and other content for toggling -->
<div id="huxblog_navbar">
<div class="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
······
<!-- newly added -->
<li class="search-icon">
<a href="javascript:void(0)">
<i class="fa fa-search"></i>
</a>
</li>
<!-- newly added end -->
</ul>
</div>
</div>
<!-- /.navbar-collapse -->
······
然后我们新建一个_includes/search.html
:
<!-- search.html -->
<div class="search-page">
<div class="search-icon-close-container">
<span class="search-icon-close">
<i class="fa fa-chevron-down"></i>
</span>
</div>
<div class="search-main container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<form></form>
<input type="text" id="search-input" placeholder="$ grep...">
</form>
<div id="search-results" class="mini-post-list"></div>
</div>
</div>
</div>
</div>
这个页面就是我们点击导航栏的搜索 icon 后弹出的页面,其内容分为两块,一个是向下的箭头图标 icon,点击它我们会回到原来的页面;然后就是搜索框和结果展示的部分。
这两处我们都使用了Font Awesome提供的图标(fa fa-search
和fa fa-chevron-down
)
最后在default.html
中引入搜索页面search.html
,在body
标签下添加一句: {% include search.html %}
实现点击搜索
不出意外的话,现在的导航栏上方会出现一个空白区域,那就是我们写的search.html
,而右上角则是我们的导航栏和搜索图标(可能因为是白色融于背景看不见)。
接下来我们编写点击事件,点击搜索 icon,焦点给到输入框,实现动态搜索功能。
要如何实现动态搜索呢?这里我们参考了开源库Simple-Jekll-Search,它能为一个 Jekyll 博客提供搜索功能。其仓库中提供了示例(example 目录),有兴趣的话可以去进一步查看其使用。
总之,我们仿照它,新建js/simple-jekyll-search.min.js
。内容有点长而且是.min.js
的压缩 js 文件,可读性差,就不贴代码了,具体内容可以去我仓库的源文件复制一下:/js/simple-jekyll-search.min.js
有了simple-jekyll-search.min.js
后,我们需要在_includes/footer.html
中将其引用:
......
<!-- in footer.html -->
<!-- Bootstrap Core JavaScript -->
<script src="/js/bootstrap.min.js "></script>
<!-- newly added -->
<!-- Simple Jekyll Search -->
<script src="/js/simple-jekyll-search.min.js"></script>
<!-- newly added end -->
<!-- Custom Theme JavaScript -->
<script src="/js/hux-blog.min.js "></script>
......
然后在根目录下创建search.json
文件,作为本地搜索结果的一个小数据源及其格式(开头的layout: null
是告诉 Jekyll 让它别解析成其他文件了)
---
layout: null
---
[
{% for post in site.posts %}
{
"title" : "{{ post.title | escape }}",
"subtitle" : "{{ post.subtitle | escape }}",
"tags" : "{{ post.tags | join: ', ' }}",
"url" : "{{ site.baseurl }}{{ post.url }}",
"date" : "{{ post.date }}"
} {% unless forloop.last %},{% endunless %}
{% endfor %}
]
最后通过 JS 实现点击弹出和关闭搜索页面的功能,这里写在了_includes/footer.html
的最下面,虽然不是很合规,不过可以保证我们页面渲染完了才进行绑定,避免出现控件还没渲染我们就找它的类名所导致的错误。
<!-- in footer.html -->
<!-- Simple Jekyll Search -->
<script>
function htmlDecode(input) {
var e = document.createElement("textarea");
e.innerHTML = input;
//if empty input
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
SimpleJekyllSearch({
searchInput: document.getElementById("search-input"),
resultsContainer: document.getElementById("search-results"),
json: "/search.json",
searchResultTemplate:
'<div class="post-preview item"><a href="{url}"><h2 class="post-title">{title}</h2><h3 class="post-subtitle">{subtitle}</h3><hr></a></div>',
noResultsText: "No results",
limit: 50,
fuzzy: false,
templateMiddleware: function (prop, value, template) {
if (prop === "subtitle" || prop === "title") {
if (value.indexOf("code")) {
return htmlDecode(value);
} else {
return value;
}
}
},
});
$(document).ready(function () {
var $searchPage = $(".search-page");
var $searchOpen = $(".search-icon");
var $searchClose = $(".search-icon-close");
var $searchInput = $("#search-input");
var $body = $("body");
$searchOpen.on("click", function (e) {
e.preventDefault();
$searchPage.toggleClass("search-active");
var prevClasses = $body.attr("class") || "";
setTimeout(function () {
$body.addClass("no-scroll");
}, 400);
if ($searchPage.hasClass("search-active")) {
$searchClose.on("click", function (e) {
e.preventDefault();
$searchPage.removeClass("search-active");
$body.attr("class", prevClasses);
});
$searchInput.focus();
}
});
});
</script>
可以看到这里针对两种功能提供了方法。
上面的htmlDecode()
和SimpleJekyllSearch()
是根据Simple-Jekll-Search而来的,其具体调用在simple-jekyll-search.min.js
之中。
下面的方法则是实现点击 icon 之后的弹出/收起页面的功能,比较好看懂,通过经典的 JS(j Query)和 DOM 来实现。
简单来说,当我们点击首页的搜索 icon后,记录当前body
标签的类名(var prevClasses = $body.attr('class') || ''
),给新页面(搜索页面)的body
加上'no-scroll'
的类名;
然后toggleClass('search-active')
会让当前页面在有'search-active'
和没有这个类名之间切换(没有则添加,有则移除)。有这个类名表示在搜索页面,没有则不在;
在这之后,会进行一个判断,如果有这个类名,则说明我们进入了搜索页面,将焦点给到输入框($searchInput.focus()
),并且为箭头 icon设置一个点击方法($searchClose.on()
),点击箭头 icon后移除页面的'search-active'
类名,并回滚body
的类名(其实就是把之前加上的'no-scroll'
去掉)
所以总的流程就是这样:
给搜索icon添加点击事件 -> 在首页点击搜索icon,打开搜索页 -> 添加'search-active'、'no-scroll'类名;给箭头icon添加点击事件 -> 在搜索页点击箭头icon,回到首页 -> 删掉'search-active'、'no-scroll'类名
添加样式
好了,现在我们首页顶上仍是有一片白色区域,点击搜索 icon 聚焦到搜索框,输入内容也能动态出现结果。
但是这片白色区域肯定不能就这样放着的,我们需要在点击搜索 icon 的时候将它弹出,这主要通过添加样式来实现。还记得我们在search.html
里设定的很多类名,以及在footer.html
中添加的'search-active'
类名吧,他们代表了整个搜索页面,所以需要给他们设置样式。
虽然东西有点多,但是样式这玩意也没什么逻辑,所以可以直接按照以下步骤复制粘贴:
- 新建
less/search.less
文件,内容有点长就不贴出来了,可以直接去复制源文件:less/search.less - 在
less/hux-blog.less
中引入search.less
,在其开头添加一行@import "search.less";
- 在
css/hux-blog.css
和css/hux-blog.min.css
中添加以下内容:
/* in hux-blog.css and hux-blog.min.css */
.search-page {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
background: #fff;
-webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);
transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);
-webkit-transform: translate(0, 100%);
-ms-transform: translate(0, 100%);
transform: translate(0, 100%);
opacity: 0;
}
.search-page.search-active {
opacity: 1;
-webkit-transform: translate(0, 0) scale(1, 1);
-ms-transform: translate(0, 0) scale(1, 1);
transform: translate(0, 0) scale(1, 1);
}
.search-page.search-active .search-main {
opacity: 1;
}
.search-page .search-main {
padding-top: 80px;
height: 100%;
opacity: 0;
-webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;
transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;
}
.search-page .search-main .row,
.search-page .search-main .row > div {
height: 100%;
}
.search-page .search-icon-close-container {
position: absolute;
z-index: 1;
padding: 16px;
top: 0;
right: 2px;
}
.search-page .search-icon-close-container i {
font-size: 20px;
}
.search-page #search-input {
font-family: "Fira Code", Menlo, Monaco, Consolas, "Courier New", monospace;
border: none;
outline: none;
padding: 0;
margin: 0;
width: 100%;
font-size: 30px;
font-weight: bold;
color: #404040;
}
@media only screen and (min-width: 768px) {
.search-page #search-input {
margin-left: 20px;
}
}
.search-page #search-results {
overflow: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
padding-bottom: 80px;
}
.search-icon a,
.search-icon-close {
cursor: pointer;
font-size: 30px;
color: #311e3e;
-webkit-transition: all 0.25s;
transition: all 0.25s;
}
.search-icon a:hover,
.search-icon-close:hover {
opacity: 0.8;
}
.search-icon,
.search-icon-close {
font-size: 16px;
}
这里解释一下几个文件类型,css
是我们熟悉的样式文件;.min.css
表示压缩后的 css 文件,它通常去掉了去掉多余的注释、空格等,文件较小,易于加载,但是可读性差,通常没有空格和换行。所以其实hux-blog.min.css
和hux-blog.css
的内容是一样的。在 VSCode 中,我们可以通过 Minify 插件快速根据 .css
文件生成 .min.css
,就不需要自己手动修改 .min.css
了。
.less
则是 Less 这种预处理 css 的语言的文件,和Saas 类似,允许使用变量、编写函数等,让 css
的编写更高效。
取消搜索页的外部滚动
这个时候我们的弹出/收起搜索页面和简单的搜索功能已经实现了,搜索结果的文章会随着我们在搜索框的输入动态更新。如果匹配的文章很多的话,我们可以滚动查看。
但是还有美中不足的一点,就是外部页面的滚动条仍然存在,我们仍然可以对外部页面进行滚动,虽然在视觉上没有什么影响,但是当我们推出搜索页面,就会发现外部页面的位置改变了。
为此,我们需要取消外部页面的滚动,这时候,我们之前在footer.html
中所生成的'no-scroll'
类名就派上用场了。
从上面的代码可知,我们实际上在进入搜索页面前记录了外部页面body
的类名(实际上body
没有类名),然后在进入搜索页面后为body
设置了'no-scroll'
类名,最后退出搜索页面的时候回滚body
的类名(变成进入前没有类名的状态)
所以'no-scroll'
这个全新的类名就是用来取消外部滚动的,我们只用实现这个类名的样式即可。我们在hux-blog.css
、hux-blog.less
和hux-blog.min.css
这三个样式文件中加入以下代码即可:
/* in hux-blog.css, hux-blog.less, hux-blog.min.css */
.no-scroll {
overflow-y: hidden;
}
最后
现在咱们预想的搜索功能就算实现啦
刷新一下博客来测试一下吧,记得要删掉缓存哦~