博客养成记(2) – GoinOne Hugo主题

内容管理

图片

Markdown 的原生语法是在文章中引入图片最便捷的方法,但是原生引用的图片默认居左对齐,如果我们想要某些图片以 fancybox 的形式呈现或者居中对齐,最好是自己创建 shortcodes 模板。shortcodes 是一种短代码,是用于在 Markdown 文章中调用 hugo 内置或主题自定义模板的代码段。Markdown 文章格式简洁,能够让人专注于写作,也能够满足大部分文章需求。但如果想要在文章中实现更多的格式(比如图片居中、甘特图、上标等),通常要借助于 HTML 标记。加入 HTML 便与 Markdown 的简洁性相悖,短代码的引入则是两者的折中之法。

创建 figure 模板

我希望 figure 短代码能够默认实现图片居中,而且提供 fancybox 支持。实现步骤如下:

1、在主题文件夹的 /layouts/shortcodes/ 新建一个 figure.html,figure 这个文件名便是可供调用的短代码名。

2、下载并引用 fanbox3 资源文件。

3、写入 figure.html 文件的 shortcodes 内容:

 {{/* Enable image to be loaded from local page src or media library at `static/images/`. */}}
 {{ $image_src := .Get "src" }}
 ​
 {{/* 如果 figure 短代码带有 library="true" ,则定位到 static/images/ */}}
 {{ if .Get "library" }}
   {{ $image_src = printf "images/%s" $image_src | relURL }}
 {{ end }}
 ​
 {{/* 默认启用fancybox ? */}}
 {{ $lightbox := eq (.Get "lightbox" | default "true") "true" }}
 ​
 {{/* Get lightbox group for showing multiple images in a lightbox. */}}
 {{ $group := .Get "lightbox-group" | default "" }}
 ​
 {{/* Get caption. Support legacy `title` option. */}}
 {{ $caption := .Get "title" | default (.Get "caption") | default "" }}
 ​
 {{/* 默认居中 */}}
 {{ $centerbox := eq (.Get "centerbox" | default "true") "true" }}
 ​
 <figure{{ with .Get "class" }} class="{{.}}"{{ end }}>
 <div{{ if $centerbox }} class="flex-center w-100" {{ end }}>
 ​
 {{ if $lightbox }}
   <a data-fancybox="{{$group}}" href="{{$image_src}}" {{ with $caption }}data-caption="{{ .|markdownify|emojify }}"{{ end }}>
 {{ else if .Get "link"}}
   <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{.}}"{{ end }}{{ with .Get "rel" }} rel="{{.}}"{{ end }}>
 {{ end -}}
 ​
 <img src="{{$image_src}}" alt="{{ with .Get "alt" }}{{.}}{{end}}" {{ with .Get "width" }}width="{{.}}" {{end}}{{ with .Get "height" }}height="{{.}}" {{end}}>
 ​
 {{- if or $lightbox (.Get "link") }}</a>{{ end }}
 </div>
 {{ if $caption }}
   {{/* Localize the figure numbering (if enabled). */}}
   {{ $figure := split (i18n "figure" | default "Figure %d:") "%d" }}
   <figcaption{{ if eq (.Get "numbered") "true" }} data-pre="{{ index $figure 0 }}" data-post="{{ index $figure 1 }}" class="numbered"{{ end }}{{ if $centerbox }} class="flex-center w-100" {{ end }}>
     {{ $caption | markdownify | emojify }}
   </figcaption>
 {{ end }}
 </figure>

使用 figure

文章图片可以保存在 static/images/中,亦可与文章在同一目录中,不同位置的图片调用路径格式略有差别。在 Markdown 文章中引用图片有以下几种方式:

1、Markdown 原生引用:

![alternative text for search engines](/images/image.jpg)

2、使用 figure 短代码引用 static/images/中的图片(set library="true"):

{{</* figure src="/images/image.jpg" */>}}

3、使用 figure 短代码引用文章同目录下的图片:

{{</* figure src="../image.jpg" */>}}

e.g. 本文 md 源文件位于 content/sci-tech/2019-10/,引用同目录下的 all-in-cloud.jpg 图片语法如下:

{{</* figure src="../all-in-cloud.jpg" */>}}

引用 static/images/ 目录下的 all-in-cloud.jpg 图片语法如下:

{{</* figure src="/images/all-in-cloud.jpg" */>}}

figure 引入的图片默认启用 fancybox,使用 lightbox="false"属性可以取消 fancybox 。

figure 引入的图片默认居中,使用centerbox="false"属性可以取消居中。

画廊

创建 gallery 模板

 {{/* Put this file as /layouts/shortcodes/gallery.html */}}
 {{ $baseURL := .Site.BaseURL | absURL }}
 {{ $pageDir := .Page.File.Dir}}
 {{ $filePath := "" }}
 {{/* /static/ image? */}}
 {{ $library := eq (.Get "library" | default "true") "true" }}
 ​
 {{/* generate figures for all of the images in the directory */}}
 {{ if $library }}
  {{ $filePath = print "/static/" (.Get "album")}}
 {{ else }}
  {{ $filePath = print "/content/" $pageDir (.Get "album") "/"}}
  {{$pageDir}}
 {{ end }}
 <div class="gallery col-lg-10 offset-lg-1">
  {{ with (.Get "album") }}
    {{ if (fileExists $filePath) }}
      {{ $files := readDir ($filePath) }}
      {{- range $files -}}
        {{ $linkURL := ""}}
        {{/* is the current file an image? */}}
        {{- $isimg := lower .Name | findRE "\\.(gif|jpg|jpeg|tiff|png|bmp)" }}
        {{- if $isimg }}
          {{/* humanized filename without extension */}}
          {{- $caption := .Name | replaceRE "\\..*" "" | humanize }}
        {{ if $library }}
          {{/* absolute URL to hi-res image */}}
          {{- $linkURL = print $baseURL ($.Get "album") "/" .Name | absURL }}
        {{ else }}
          {{/* absolute URL to hi-res image */}}
          {{- $linkURL = print "../gallery/" .Name }}
      {{ end }}
       <a data-fancybox="" href="{{ $linkURL }}" {{ with $caption }}data-caption="{{ .|markdownify|emojify }}" {{ end }}>
         <img src="{{ $linkURL }}" alt="">
       </a>
      {{ end }}
      {{- end -}}
    {{ else }}
      {{ errorf "path not exist: %s in %s." $filePath $.Position }}
    {{ end }}
  {{ else }}
    {{/* If no directory was specified, include any figure shortcodes called within the gallery */}}
    {{ errorf "Unable to load gallery in %s." .Position }}
  {{ end }}
 </div>

使用 gallery

假设画廊文件夹为 static/images/gallery-theme/,则引用方式如下:

 {{</* gallery album="/images/gallery-theme/" caption="gallery caption" */>}}

上述代码引入的画廊效果如下所示:

{{< gallery album=”/images/gallery/” caption=”gallery caption” >}}

参考:

由于 gallery 功能需要遍历指定的画廊文件夹,因此在遍历前要先判断文件夹是否存在,实现参考自:Hugo’s fileExists function · Kodify https://kodify.net/hugo/functions/hugo-fileexists-function/

gallery.html 中同时用到了 Hugo的=:=,这两个符号的区别参考:https://stackoverflow.com/questions/17891226/difference-between-and-operators-in-go,总的来说,如果要快速在一个 block 里声明一个变量,直接使用 :=,如果是要重新对已有的变量赋值,则使用=

公式

Hugo 默认没有支持 Latex 公式,本站目前对公式页还没有大的需求,先占个坑,等要用到再加上。

[1] 在Hugo中使用MathJax https://note.qidong.name/2018/03/hugo-mathjax/

[2] Supported Content Formats | Hugo https://gohugo.io/content-management/formats/#mathjax-with-hugo

[3] Setting MathJax with Hugo https://divadnojnarg.github.io/blog/mathjax/

功能补充

搜索功能

暂时使用百度站内搜索。hugo 自带的或者其他第三方本地搜索方案在中文的分词效果上实在是差强人意。

文章归档

Hugo(0.58.3) 目前还没有自动生成归档功能,需要我们自己实现。

1 layouts/archives/single.html

首先在主题下创建layouts/archives/single.html文件,然后在这里面编写生成归档功能的逻辑和样式:

{{ define "main" }}
 <main class="home-main-wrapper px-sm-0 px-md-2 px-lg-5 px-xl-5 default-archives">
     <div class="row">
         <!-- Post list -->
         <div class="col-md-9 mt-3">
             <ul>
                 <div class="div-x">
                     {{ partial "page-heading.html" . }}
                     {{ $page_num := (len ((where (where .Site.Pages "Type" "not in" (slice "about" "archives" "home")) "Kind" "page"))  )}}
                     {{ if gt $page_num 0 }}
                     <div class="px-2 middot"></div>
                     <div>
                         {{ i18n "postCount" $page_num }}
                     </div>
                     {{ end }}
                 </div>
                 {{ if .Content }}
                 <div class="post-content markdown">
                     {{ .Content }}
                 </div>
                 {{ end }}
             </ul>
             <ul>
                 <div class="post-archive">
                     {{ range (where (where .Site.Pages "Type" "not in" (slice "about" "archives" "home")) "Kind" "page").GroupByDate "2006" }}
                     <li class="post-item no-bullet">
                         <span class="date">{{ .Key }}</span>
                     </li>
                         {{ range .Pages }}
                             {{ partial "li.html" . }}
                         {{ end }}
                     {{ end }}
                 </div>
             </ul>
         </div>
         <!-- /Post list -->
         <!-- Sidebar -->
         <div class="col-md-3 d-none d-md-block">
             {{ partial "sidebar-categories.html" . }}
             {{ partial "sidebar-tags.html" . }}
             {{ partial "sidebar-series.html" . }}
         </div>
         <!-- /Sidebar -->
     </div>
 </main>
 {{end}}

2 content/archives/index.md

在hugo站点根目录创建content/archives/index.md

 ---
 title: "文章归档"
 description: 文章列表
 type: archives
 ---

front matter 里的titledescription 可以自定义,type 则必须为 archives,该属性告诉 Hugo 使用 archives/single.html 模板渲染本文件。渲染生成的文件为 public/archives/index.html,之后就可以通过 migchar.com/archives/index.html 访问归档页面。

在 Hugo 模板中,我使用了以下语句去统计文章的数量,该语句表示统计 content 下除了 about、archives、home这3个文件夹之外的文章数量。

{{ $page_num := (len ((where (where .Site.Pages "Type" "not in" (slice "about" "archives" "home")) "Kind" "page"))  )}}

使用以下语句去遍历文章,该语句表示遍历 content 下除了 about、archives、home这3个文件夹之外的文章。

{{ range (where (where .Site.Pages "Type" "not in" (slice "about" "archives" "home")) "Kind" "page").GroupByDate "2006" }}

Back to Top

需求:当页面篇幅较多时,希望右下角有一个滚动到顶端的按钮。

实现步骤:

1、在需要使用该功能地方加入以下 div,位置没有讲究,只要包含在页面的主 div 中即可。

<div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
</div>

我只在主题下的 layouts/index.html 跟 layouts/list.html 中加入以上代码块,这样 Hugo 渲染出来就只有首页以及菜单模块 List 页面会出现滚动到顶端的按钮。

2、样式控制 css

.back-to-top {
     box-sizing: border-box;
     cursor: pointer;
     position: fixed;
     right: 50px;
     top: -900px;
     z-index: 2;
     width: 70px;
     height: 900px;
     background: url('/img/scroll.png');
     transition: all .5s ease-in-out;
     opacity: 1;
}
// 小屏幕隐藏按钮
@media screen and (max-width: 58rem){
     .back-to-top{
         display: none;
     }
}

3、行为控制 scroll.js

 // 利用 data-scroll 属性,滚动到任意 dom 元素
 $.scrollto = function (scrolldom, scrolltime) {
     $(scrolldom).click(function () {
         $(this).addClass("active").siblings().removeClass("active");
         $('html, body').animate({
             scrollTop: $('body').offset().top
         }, scrolltime);
         return false;
     });
 };
 // back-to-top 按钮出现的时机
 var backTo = $(".back-to-top");
 var backHeight = $(window).height() - 1080 + 'px';
 $(window).scroll(function () {
     if ($(window).scrollTop() > window.innerHeight
         && backTo.css('top') === '-900px') {
         // 隐藏 back-to-top 按钮
         backTo.css('top', backHeight);
     } else if ($(window).scrollTop() <= window.innerHeight
         && backTo.css('top') !== '-900px') {
         // 显示 back-to-top 按钮
         backTo.css('top', '-900px');
     }
 });
 ​
 // 启用 back-to-top
 $.scrollto(".back-to-top", 800);

这里有一个细节,因为本站的首页有一个 banner,高度占满一个页面高度,我希望只有当用户向下滚动看到首页内容区域时才显示 back-to-top(即滚过至少一个页面高度时才显示 back-to-top 按钮)。实现这个功能用到的是 $(window).scrollTop() > window.innerHeight这个判断条件。`$(window).scrollTop()计算当前滚动条距离顶端的垂直高度,window.innerHeight计算浏览器当前页面可用高度,当前者大于后者的值时,说明页面滚动了至少一个页面的高度,此时就是 back-to-top 按钮出现的时机。

参考:

[1] innerHeight() | jQuery API Documentation

隐藏导航栏

需求:现在显示器屏幕很多都是大宽屏,跟宽度相比显示区域的高度会更小,固定页顶的导航栏会占用一部分内容显示区域,要是页面在向下滚动时隐藏导航栏,向上滚动时显示导航栏,则能够让用户关注到更多的页面内容。

原理:这里用到 Headroom.js,这是个轻量级的库,只占 1.7KB,作用是在页面滚动时对页面 document 做出响应。原理是在页面滚动时为需要响应滚动事件的页面元素增加或删除 CSS 元素,我们只需要对这些 CSS 元素的样式进行设置就可以实现页面变化。

实现步骤:

1、引入 headroom.js

2、调用 Headroom:

 // 创建 Headroom 对象并传入要控制的页面元素
 var headroom = new Headroom($('.site-nav')[0], {
     "tolerance": 5,
     "offset": 205,
     "classes": {
         // 初始化后所设置的 css class
         "initial": "head-animated",
         // 向上滚动时设置的 css class
         "pinned": "slideDown",
         // 向下滚动时所设置的 css class
         "unpinned": "slideUp"
     }
 });
 // 初始化
 headroom.init();

3、CSS样式设置。

.head-animated.slideUp {
    -webkit-animation-name: slideUp;
    -moz-animation-name: slideUp;
    -o-animation-name: slideUp;
    animation-name: slideUp;
    transform: translateY(-100%);
}
.head-animated.slideDown {
    -webkit-animation-name: slideDown;
    -moz-animation-name: slideDown;
    -o-animation-name: slideDown;
    animation-name: slideDown;
    transform: translateY(0%);
}
.head-animated {
    -webkit-animation-duration: .5s;
    -moz-animation-duration: .5s;
    -o-animation-duration: .5s;
    animation-duration: .5s;
    -webkit-animation-fill-mode: both;
    -moz-animation-fill-mode: both;
    -o-animation-fill-mode: both;
    animation-fill-mode: both;
    will-change: transform;
    transition: transform 200ms linear;
}

这里用到了 css 3 的transform: translateY属性,其作用是将元素进行 Y 轴(垂直方向)移动。

当页面向下滚动(unpinned)时,Headroom 为导航栏 .site-nav新增一个 ”slideUp“的 css class,slideUp样式为 transform: translateY(-100%),表现为导航栏沿 Y 轴向上平移一个自身高度(100%),即导航栏隐藏了。

当页面向上滚动时(pinned)时,Headroom 为导航栏 .site-nav新增一个 ”slideDown“的 css class,slideUp样式为 transform: translateY(-100%),表现为导航栏沿 Y 轴向下平移 0%(与导航栏初始状态相比),导航栏垂直位置不变,即向上滚动时导航栏为初始时的显示状态。

参考资料:

[1] 为页面顶部多留些空间,在不需要页头时将其隐藏:WickyNilliams/headroom.js: Give your pages some headroom. Hide your header until you need it https://github.com/WickyNilliams/headroom.js

[2] Headroom 效果展示:Headroom.js playground https://www.bootcss.com/p/headroom.js/playroom/

细节修复

修复移动端向左滑动时右侧出现空白

问题描述:在移动端,页面向左滑动时右侧出现空白;在小宽度显示器上,页面右侧也出现空白,并有横向滚动条。问题页面如下图所示。

mobile-right-white-space

原理:出现这个情况一般是由于页面某个内部元素宽度溢出,撑开了页面。解决问题的关键在于排查出是哪个元素溢出了。

排查方法:按 F12 打开浏览器开发控制台,输入以下代码并回车:

[].forEach.call($$("*"),function(a){
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
})

上面所示代码的作用是为页面所有元素加上颜色边框,接下来只需要从页面顶部慢慢滚动到底部,仔细观察,通过这些不同颜色的边框可以很直观地看到是哪个元素撑破了页面(表现为元素顶出了页面的右边界)。修复这个溢出的元素就可以解决页面右侧空白的问题。

如下图所示,可以看到是一个超链接撑破了页面,这是由于 <a> 链接过长没有自动换行造成的。

mobile-right-white-space

解决方法:<a> 链接加上自动换行的样式,这里我是直接为全文样式.post-content添加自动换行,代码如下。

.post-content {
    word-break: break-all;
    overflow-wrap: break-word;
}

有时候添加以上样式仍未自动换行,需要检查其他子元素是否设置了更为优先的不换行样式。

参考资料:

word-break – CSS | MDN

overflow-wrap – CSS | MDN

white-space – CSS | MDN