6

博客优化:Github Page博客添加搜索功能

 1 year ago
source link: https://blackdn.github.io/2023/01/16/Search-in-Blog-2023/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

“如白驹过隙,转瞬即逝。如山岳层峦,连绵不绝。”

博客优化: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对其使用作出了限制,包括但不限于以下几点:

  1. GitHub Pages的存储仓库建议不超过1GB(软限制,Github对每个仓库都有这个建议,毕竟太大克隆都要下载半天)
  2. GitHub Pages的发布站点不能超过1GB(硬限制,但是我要是能写文章超过1GB,估计那会儿头发也掉光了)
  3. 带宽限制每月100GB(软限制)
  4. 限制站点每小时 10 次生成(软限制)
  5. 在高并发或流量高峰期会限制速率

具体内容可见Github Pages使用限制
但是要我说,还是钱钱重要,什么流量容量限制,对我这种没几篇文章,文章也没几个字的小众人物来说跟没有一样嘛。钱钱💰,我的钱钱💰

实现搜索功能

如果我的gif能正常加载的话,咱们的一个简单的搜索功能大概是下面这样:
导航栏的右上角多了个搜索图标,点击后弹出一个搜索页面;在搜索框中输入会动态检索匹配的文章,并允许我们点击跳转。

preview

假设我们是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-searchfa 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'类名吧,他们代表了整个搜索页面,所以需要给他们设置样式。

虽然东西有点多,但是样式这玩意也没什么逻辑,所以可以直接按照以下步骤复制粘贴:

  1. 新建less/search.less文件,内容有点长就不贴出来了,可以直接去复制源文件:less/search.less
  2. less/hux-blog.less中引入search.less,在其开头添加一行@import "search.less";
  3. css/hux-blog.csscss/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.csshux-blog.css的内容是一样的。(不过我们这个博客样式体量比较小所以我好像也把它格式化了)
.less则是Less这种预处理css的语言的文件,和Saas是同一种类,允许使用变量、编写函数等,让css的编写更高效。

取消搜索页的外部滚动

这个时候我们的弹出/收起搜索页面和简单的搜索功能已经实现了,搜索结果的文章会随着我们在搜索框的输入动态更新。如果匹配的文章很多的话,我们可以滚动查看。
但是还有美中不足的一点,就是外部页面的滚动条仍然存在,我们仍然可以对外部页面进行滚动,虽然在视觉上没有什么影响,但是当我们推出搜索页面,就会发现外部页面的位置改变了。
为此,我们需要取消外部页面的滚动,这时候,我们之前在footer.html中所生成的'no-scroll'类名就派上用场了。

从上面的代码可知,我们实际上在进入搜索页面前记录了外部页面body的类名(实际上body没有类名),然后在进入搜索页面后body设置了'no-scroll'类名,最后退出搜索页面的时候回滚body的类名(变成进入前没有类名的状态)
所以'no-scroll'这个全新的类名就是用来取消外部滚动的,我们只用实现这个类名的样式即可。我们在hux-blog.csshux-blog.lesshux-blog.min.css这三个样式文件中加入以下代码即可:

/* in hux-blog.css, hux-blog.less, hux-blog.min.css */
.no-scroll {
  overflow-y: hidden;
}

现在咱们预想的搜索功能就算实现啦
刷新一下博客来测试一下吧,记得要删掉缓存哦~



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK