加载中...
返回

Hugo Stack主题更新小记

近期一好友搭建博客,使用了跟我一样的 Hugo Stack主题 ,在自定义的过程当中痛不欲生。我才发现JimmyCai大佬已经把这个主题更新了好几版,无怪乎我旧有的一些经验已经不起作用了。

今天下载了 3.6.0 版本的 Release ,打算这几天把作者更新出来的一些好东西和我之前自己修改的一些东西结合一下,两者都有所扬弃,总的来说就是希望把我这个自娱自乐的博客弄得再好看一些。

动工前纪念

动工之前,把旧有的博客样式和 未作修改之前的 新版Stack样式进行一下合影留念,也算是这个博客样貌成长的一个里程碑吧。其实我想最终的样子跟现有的样子相比,不会有很大的变化,但应该能够把我此前很不爽且无能为力的一些细节消灭掉,后文细说~

Part 1 左边栏

图标

图标显示

修改配置文件和 page

首先,可以看到新版的主题左边栏的几个跳转按钮(后文使用 menu 来指代这几个东西)是没有图标的,光秃秃的很不好看。

发生这种情况的原因是新版的 menu 图标与旧版的图标不同。在旧版主题中, menu 图标由 menu.main.pre 来指定,而新版的图标由 menu.main.params.icon 来指定,参数都不一样了,图标丢失是当然的了!

根据作者的 文档 ,添加页面有两种方式,一种是在 content/page 页面的 Front Matter 中加上 menu.main 参数,另一种是在 config.yaml 文件中添加 menu.main 参数。旧有的主题也是这么工作的, 我们只需要把跟图标相关的参数修改一下就行了

config.yaml 中的内容为例,以前的 menu 是这样的:

menu:
    main:
        - identifier: home
          name: 博文
          url: /
          weight: -100
          pre: home

修改过后,要变成这样:

menu:
    main:
        - identifier: home
          name: 主页
          url: /
          weight: -100
          params:
            icon: home

page 当中的那些页面也要如此修改。

于是我们的图标就回来了!

图标高亮

如果当前页面对应左边栏的某个入口,那么左边栏的图标应该高亮显示。

新版的主题只有一点小问题,那就是主页的图标没办法高亮。

layouts/partials/sidebar/left.html 中找到了图标高亮的相关代码,从第 56 行开始:

<ol class="menu" id="main-menu">
        {{ $currentPage := . }}
        {{ range .Site.Menus.main }}
        {{ $active := or (eq $currentPage.Title .Name) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }}

        <li {{ if $active }} class='current' {{ end }}>
            <a href='{{ .URL | relLangURL }}' {{ if eq .Params.newTab true }}target="_blank"{{ end }}>
                {{ $icon := default .Pre .Params.Icon }}
                {{ if .Pre }}
                    {{ warnf "Menu item [%s] is using [pre] field to set icon, please use [params.icon] instead.\nMore information: https://docs.stack.jimmycai.com/configuration/custom-menu.html" .URL }}
                {{ end }}
                {{ with $icon }}
                    {{ partial "helper/icon" . }}
                {{ end }}
                <span>{{- .Name -}}</span>
            </a>
        </li>

注意代码片段中的第四行,即控制 active 这个属性的部分,当 activetrue 时,左边栏的对应图标就高亮了。

什么情况下 active 会是 true 呢?作者在这边写的语句比较复杂,其实只有两种情况:

  • 当前页面的 标题 和某个 menu.name 相同;
  • 当前页面可以在 Menus 菜单中找到,即当前页面属于从 menu 菜单可以访问的页面。

在本地测试中,无论从 config 文件添加主页入口,还是从 page 目录下添加主页入口,主页都是 不高亮 的。看来在Hugo中主页不属于 menu 菜单的一部分。

active 的原理基本上弄清,理论上可以将主页入口也设置成高亮;但是时间有限且难以找到一个合适的逻辑,因此暂时不考虑这个细节。


2022-01-15更新

我们重新梳理一下上面的代码逻辑:对于任意一个页面,首先获取页面对象本身,然后对全站的所有 Menus 进行遍历,记当前遍历到的菜单对象为 menu ,如果当前菜单符合某种条件,就将其高亮。

当:

  • 当前页面的 标题menu.name 相同的时候;
  • 当前页面属于 menu 菜单下的页面;

两个条件任意一个成立,我们遍历到的 menu 组件就产生高亮。

正常情况下,高亮是由第一个条件产生的,即某个页面的标题与 menu.name 相同。例如,点击【归档】这个按钮,跳转到 归档 这个页面,在这个页面加载的时候,第一个条件触发,对应的名称为“归档”的那一个 menu 就显示为高亮。

而在主页下,主页的标题是网站的标题,主页的 menu.name 却是“主页”,因而第一个条件无法成立;在更早的分析中,我认为由于Hugo API的原因,使得 / 目录不被认为是某个 menu 能够达到的页面,所以在这里第二个条件也没有成立,主页的图标就不显示高亮了。

希望让主页图标高亮,光靠作者的这两个逻辑是不够的,应该为 $active 变量补充一些逻辑。

在主页,页面标题是站点标题即 XR_S's Blog ,而 menu.name主页 ,因而第一个条件不成立;在更早的分析中, 我认为由于hugo API的问题使得第二个条件对于 / 路径也不成立,因此主页的入口没有显示高亮。

那么我们应该添加什么逻辑呢?

首先我们知道主页的 menu 是添加在 config 文件中的,它的配置是:

menu:
    main:
        - identifier: home
          name: 主页
          url: /
          weight: -100
          params:
            icon: home

这里有它的独一无二的属性就是 identifier ,利用这个属性,我们可以获取到代表主页的这一个 menu 入口。

我选用的逻辑是: 假如当前的页面标题与网站标题相同,我就将主页的 menu 入口设置为高亮

因此修改 $active 部分的相关代码为:

{{ $currentPage := . }}
{{ $siteTitle := .Site.Title }}
{{ range .Site.Menus.main }}
{{ $active := or (eq $currentPage.Title .Name) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }}
{{ $active := or ($active) (and (eq $currentPage.Title $siteTitle ) (eq .Identifier "home")) }}

成功让主页菜单高亮了!

主页布局

主页的布局可以说是整个新主题当中最令人不满意的地方。

我没有美学基础,只从最基本的个人的视觉喜好出发,首先对比一下新旧两个主题主页布局的差异。

我的感受是:旧主题的页面左右端有一定的留白,显得比较和谐;新主题的页面左右端距离浏览器边框非常近,过于拥挤了。

决定将旧主题的主页布局迁移到新主题上,同时进行一些微调。

修改自适应布局

要修改主页的布局,首先应该找到控制主页样式的那个文件。它的路径是在 [themePath]/assets/scss/grid.scss ,不过我希望读者能明白我是怎么找到它的。

正如我在 这篇记录 当中所说,精确调整样式,你应该使用开发者工具选中你的目标,然后看到它的 class ,之后在项目的所有CSS文件中找到这个 class 对应的选择器,就能看到作者为它赋予了哪些样式。

例如我们要修改主页的布局,那就要用开发者工具选中主页,可以根据直觉来判断你选的对不对,一般来说一个页面会分成好几个模块,就像我们的主页那样:

我们知道这三个模块构成了主页,它的 classcontainer ;直接到CSS文件目录中查找,发现 container 这个类的样式是在 grid.scss 这个文件中定义。

开始阅读这个文件,在很靠前的地方就发现了它的重要内容哈!

.container {
    margin-left: auto;
    margin-right: auto;

    .left-sidebar {
        max-width: var(--left-sidebar-max-width);
        margin-right: 1%;
    }

    .right-sidebar {
        max-width: var(--right-sidebar-max-width);

        /// Display right sidebar when min-width: lg
        @include respond(lg) {
            display: block;
        }
    }

    &.extended {
        @include respond(md) {
            max-width: 1024px;
            --left-sidebar-max-width: 25%;
            --right-sidebar-max-width: 30%;
        }

        @include respond(lg) {
            max-width: 1280px;
            --left-sidebar-max-width: 20%;
            --right-sidebar-max-width: 30%;
        }

        @include respond(xl) {
            max-width: 1536px;
            --left-sidebar-max-width: 15%;
            --right-sidebar-max-width: 25%;
        }
    }

    === snip ===
}

我们看到,作者在这个文件中限制了 container 的子元素 left-sidebarright-sidebar 的最大宽度,分别用 --left-sidebar-max-width--right-sidebar-max-width 来表示最大宽度的限制,然后动态调整这两个变量的值,来实现对不同设备屏幕的适应。

在Stack新主题的各个CSS文件中,经常要看到 @include respond() 这种写法,它是什么东西呢?

我们找到了 asset/scss/breakpoints.scss 这个文件,它的内容是这样的:

$breakpoints: (
    sm: 640px,
    md: 768px,
    lg: 1024px,
    xl: 1280px,
    2xl: 1536px,
);

@mixin respond($breakpoint) {
    @if not map-has-key($breakpoints, $breakpoint) {
        @warn "'#{$breakpoint}' is not a valid breakpoint";
    } @else {
        @media (min-width: map-get($breakpoints, $breakpoint)) {
            @content;
        }
    }
}

啊哈,原来 respond 函数就是个CSS中的 @media 关键字的封装嘛。

例如在上面的样式中,我们看到 @include respond(md) 这样的用法,翻译翻译就是 @media(min-width: 768px) 而已!表示这个样式要在屏幕宽度 768px 以上才能生效!

看到这里,这些自适应的布局就没那么吓人了,就是 在不同的设备上指定不同的左右侧边栏宽度 而已。

我使用的笔记本电脑屏幕宽度是在 1024px 以上的,也就是说我只要修改 @include respond(lg) 底下的样式就行了。

asset/scss/custom.scss 文件中添加如下的代码:

.container {
    margin-left: auto;
    margin-right: auto;

    &.extended {
        /* range: 768-1024 */
        @include respond(md) {
            max-width: 1024px;
            --left-sidebar-max-width: 25%;
            --right-sidebar-max-width: 30%;
        }

        /* range: 1024-1280 */
        @include respond(lg) {
            max-width: 1280px;
            --left-sidebar-max-width: 25%;
            --right-sidebar-max-width: 22%;
        }
    }

    &.compact {
        @include respond(md) {
            --left-sidebar-max-width: 25%;
            max-width: 768px;
        }

        @include respond(lg) {
            max-width: 1024px;
            --left-sidebar-max-width: 20%;
        }

        @include respond(xl) {
            max-width: 1280px;
        }
    }
}

其中在我的电脑上真正起到作用的只有 @include respond(lg) 部分而已!我把左侧边栏的宽度放宽到 25% ,右侧边栏宽度放到 22% ,是我个人比较舒服的数字。

修改非自适应布局

经过上面的修改,现在的主页应该是这样的:

主要有几个问题:

  • 中间栏显得过于宽;

  • 左边栏头像太抢戏。

我们针对 left-sidebarright-sidebar 再进行一下微调,在 custom.scss 中继续追加:

.left-sidebar {
    --sidebar-avatar-size: 120px;
    --sidebar-element-separation: 20px;
    margin-right: 1%;
    padding: var(--main-top-padding) 15px;
    max-height: 100vh;
    padding-right: 25px;
}

.right-sidebar {
    margin-left: 25px;
}

这部分内容是经过旧参数的试验,发现的视觉上比较舒服的参数,而这些东西不用随着屏幕变化而变化,因此不写在刚才的自适应布局中,直接放在最外层。

至此,我的主页就调整完毕了。 custom.scss 文件全部内容如下:

.container {
    margin-left: auto;
    margin-right: auto;

    &.extended {
        /* min-width: 812 
        @media (min-width: $on-phone) {
            max-width: 800px;

            --left-sidebar-max-width: 25%;
        }
        */
        /* range: 768-1024 */
        @include respond(md) {
            max-width: 1024px;
            --left-sidebar-max-width: 25%;
            --right-sidebar-max-width: 30%;
        }

        /* range: 1024-1280 */
        @include respond(lg) {
            max-width: 1280px;
            --left-sidebar-max-width: 25%;
            --right-sidebar-max-width: 22%;
        }
    }

    &.compact {
        @include respond(md) {
            --left-sidebar-max-width: 25%;
            max-width: 768px;
        }

        @include respond(lg) {
            max-width: 1024px;
            --left-sidebar-max-width: 20%;
        }

        @include respond(xl) {
            max-width: 1280px;
        }
    }
}

/* 
跟上面 container 中的 left-sidebar 没有区别
但是这部分内容不需要使用 respond 函数,因此直接放在最外层固定下来即可
*/
.left-sidebar {
    --sidebar-avatar-size: 120px;
    --sidebar-element-separation: 20px;
    margin-right: 1%;
    padding: var(--main-top-padding) 15px;
    max-height: 100vh;
    padding-right: 25px;
}

.right-sidebar {
    margin-left: 25px;
}

在修改中,我没有完全复刻旧主题,因为我在修改过程中发现了现有布局的一些优点,放到下一小节分析。

主页视觉分析

我想指出旧主页的一个问题,这些问题在现在这个主页中是不存在了。

可以看到,旧主页中左边栏和文章列表的间距是比较大的,而文章列表和右边栏则贴得很紧,三个模块产生了一点割裂的感觉。

而在新主页中,四块留白区域宽度基本相同,左侧边栏占用的空间出让给了文章列表,突出了重点,整体上给我的感觉是比较和谐的。

Part 2 文章页面

这个文章页面是我决定更新新主题的一个重要原因。旧主题是没有右边的 目录 一栏的,我曾经仿照新主题的样式自行实现了一下,但是效果并不是特别理想:

直接更新主题之后,解决了文章样式的痛点,还是比较舒服的。考虑到这是一次大升级,我想顺便把之前的一些不足的细节补充上。

返回顶部按钮

添加函数

在阅读比较长的文章时,有一个东西是我认为非常必要的,那就是返回顶部按钮。

在新主题中,作者已经写好了Table Of Conents(即文章目录),且它的位置就在页面右侧。我希望直接在该组件的下方添加一个返回顶部的按钮,当页面不处于顶部时,该按钮出现,点击之后返回页面的顶部。

按钮的核心函数直接参考了 Slim主题 ,具体做法是在 layouts/partials/head/script.html 中添加如下的代码:

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

<script>
    // Check to see if the window is top if not then display button
    $(window).scroll(function() {
    if ($(this).scrollTop()) {
        $('#back-to-top').fadeIn();
    } else {
        $('#back-to-top').fadeOut();
    }
    });

    // Click event to scroll to top
    $('#back-to-top').click(function() {
    $('html, body').animate({scrollTop: 0}, 1000);
    return false;
    });
</script>

函数使用了 jQuery 监听窗口的滚动,并控制按钮的出现与消失,其核心是一个具备 back-to-top ID的HTML元素,在原本的主题(Slim主题)中,这个元素就是一个简单的 <a> 标签。

我希望自定义返回顶部按钮的外观,并决定使用一张图片来代替Slim主题中朴素的 ^ 号。

添加组件

此前已经提过,我希望这个按钮就放置在文章目录下方,那么我们就要找到文章目录的HTML代码,然后在它下方插入这个按钮的代码。

找到 layouts/_default/single.html ,在它的第 47 行找到了TOC的代码,在 aside 内部、TOC底部插入下面的代码:

{{ $topImg := resources.Get ("img/top.png") }}
{{ $topImg := $topImg.Resize "40x" }}
<a id="back-to-top" href="#">
    <img src="{{ $topImg.RelPermalink }}" />
</a>

这段代码是我仿照 Avatar 部分的代码写出来的,作者用类似的代码实现了主页左边栏的头像。需要把代码的图片跟头像的图片放在一起,也就是放在 [网站根目录]/assets/img 文件中。

那么,TOC部分的代码就变成了这样子:

{{ define "right-sidebar" }}
    {{ if (.Scratch.Get "hasTOC") }}
        <aside class="sidebar right-sidebar sticky">
            <section class="widget archives">
                <div class="widget-icon">
                    {{ partial "helper/icon" "hash" }}
                </div>
                <h2 class="widget-title section-title">{{ T "article.tableOfContents" }}</h2>
                
                <div class="widget--toc">
                    {{ .TableOfContents }}
                </div>
            </section>

            {{ $topImg := resources.Get ("img/top.png") }}
            {{ $topImg := $topImg.Resize "40x" }}
            <a id="back-to-top" href="#">
                <img src="{{ $topImg.RelPermalink }}" />
            </a>
        </aside>
    {{ end }}
{{ end }}
添加样式

最后一步是很简单的,我们还是需要为这个组件添加CSS样式。

找到熟悉的 custom.scss 文件,追加下面的代码:

$hover: 0.2s ease-in-out;
#back-to-top {
    bottom: -30px;
    right: 2px;
    display: none;
    position: absolute;  
    border: 0;
    transition: transform $hover;
    &:hover {
      transform: translateY(-10px);
    }
}

这里只需要注意两个重点(也就是我在添加这个组件过程中踩坑的点):

  • position: absolute ,能够将按钮固定,且它的父元素是我们上面看到的 <aside> 标签,就能将按钮跟TOC的相对位置固定起来(因为他们同属于一个父元素);如果使用 position: fixed 就不能起到这个效果,因为 fixed 属性会直接将它的父元素设置为浏览器窗口。
  • bottom: -30px ,使得这个按钮不会被TOC的边界包住,而且在屏幕缩放时不会超出屏幕底端(其实就是个试验值)。

至此就得到了一个很漂亮的返回顶部按钮了!

字体样式修改

把这一项归在文章页面似乎不太合适,因为主页的字体样式也被我更改了;但其实字体样式主要地还是与文章有关,好的字体让人看起文章来心情愉悦。

作者在 layouts/partials/footer/components/custom-font.html 中进行了字体的自定义:

<script>
    (function () {
        const customFont = document.createElement('link');
        customFont.href = "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap";

        customFont.type = "text/css";
        customFont.rel = "stylesheet";

        document.head.appendChild(customFont);
    }());
</script>

他使用的字体是 Lato ,这个字体不支持中文,因此最终呈现出来的字体是平平无奇的微软雅黑(其实是作者放置的默认选项)。

更改字体的方式有几种:

  • 使用在线字体;
  • 下载字体CSS并引入;
  • 使用正常PC都会有的字体(如微软雅黑、仿宋、楷体等)。

我决定使用在线字体,首先是因为在线字体的样式比较丰富,其次是字体CSS并没有那么好找……

科学上网 去到 Google fonts ,找到满意的中文字体,把 custom-font.html 中的原有代码注释掉,使用网站给出的代码。

我现在的 custom-font.html 内容如下:

<!-- <script>
    (function () {
        const customFont = document.createElement('link');
        customFont.href = "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap";

        customFont.type = "text/css";
        customFont.rel = "stylesheet";

        document.head.appendChild(customFont);
    }());
</script> -->

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Long+Cang&family=Ma+Shan+Zheng&family=Noto+Sans+SC:wght@300&family=Noto+Serif+SC:wght@300&family=Zhi+Mang+Xing&display=swap" rel="stylesheet">

一次性引入了几款我认为还不错的字体,以便自由选择。

引入字体之后,来到我们的老朋友 custom.scss 文件中,把 body.article-content 的字体改为我们引入的字体的名字即可。

body, .article-content {
    // font-family: 'Long Cang', cursive;
    // font-family: 'Ma Shan Zheng', cursive;
    // font-family: 'Noto Sans SC', sans-serif;
    font-family: 'Noto Serif SC', serif;
    // font-family: 'Zhi Mang Xing', cursive;
}

目录项紧凑

作者默认的文章页面是这样的:

虽然我很喜欢这个Table Of Content的位置,但我认为每一个目录之间的距离有点太开了。对于目录短一点的文章来说还好,对于目录很长的文章来说,这么大的间距显得不够精致。

老规矩,开发者工具选中、找class、搜索一条龙,在 assets/scss/partials/layout/article.scss 中找到了控制TOC格式的代码:

.widget--toc {
    background-color: var(--card-background);
    border-radius: var(--card-border-radius);
    box-shadow: var(--shadow-l1);
    display: flex;
    flex-direction: column;
    color: var(--card-text-color-main);
    overflow: hidden;

    #TableOfContents {
        overflow-x: auto;
        max-height: 75vh;

        ol,
        ul {
            margin: 0;
            padding: 0;
        }

        ol {
            list-style-type: none;
            counter-reset: item;

            li:before {
                counter-increment: item;
                content: counters(item, ".") ". ";
                font-weight: bold;
                margin-right: 5px;
            }
        }

        & > ul {
            padding: 0 1em;
        }

        li {
            margin: 15px 20px;
            padding: 5px;

            & > ol,
            & > ul {
                margin-top: 10px;
                padding-left: 10px;
                margin-bottom: -5px;

                & > li:last-child {
                    margin-bottom: 0;
                }
            }
        }
    }
}

只需要把重点放在诸如 paddingmargin 一类的字眼上即可。

审视 #TableOfContents 选择器底下的几个属性(已经删去较不重要的样式):

#TableOfContents {
    max-height: 75vh;  // 控制目录的最大高度

    li {
        margin: 15px 20px;  // 控制列表项之间的距离(重要!)
        padding: 5px;

        & > ol,
        & > ul {
            margin-top: 10px;  // 控制子列表和父列表项之间的距离(当TOC不只有一层目录的时候有用)
            padding-left: 10px;  // 子列表项的向右缩进距离(当TOC不只有一层目录的时候有用)
            margin-bottom: -5px;

            & > li:last-child {
                margin-bottom: 0;
            }
        }
    }
}

经过一番修改,我在 custom.scss 中添加了如下的样式:

.widget--toc {
    #TableOfContents {
        max-height: 65vh;

        ol,
        ul {
            list-style-type: none;
        }

        li {
            margin: 10px 10px;

            & > ol,
            & > ul {
                margin-top: 10px;
                padding-left: 10px;
                margin-bottom: -5px;

                & > li:last-child {
                    margin-bottom: 0;
                }
            }
        }
    }
}

主要解决了几个问题:

  • 目录太高了,使得之前添加的返回顶部按钮可能会被顶出屏幕;
  • 目录项前面的小黑点不太好看,隐去;
  • 目录项之间的距离不够紧凑。

进行一下对比,个人是满意这个效果的:

滚动条美化

尽管经过了紧凑,目录还是可能出现过长的情况,而原本的样式中目录的滚动条毫无疑问是粗糙的,甚至令人很难接受。

网络上有非常多自定义滚动条的教程,但是我懒得自行调试,直接使用Cabriel大佬在 这篇文章 中给出的 代码 ,将代码中自定义滚动条的部分加到上一小节中TOC的样式里,使得上一小节里修改过的TOC样式变成下面的样子:

.widget--toc {
    #TableOfContents {
        max-height: 65vh;

        ol,
        ul {
            list-style-type: none;
        }

        li {
            margin: 10px 10px;

            & > ol,
            & > ul {
                margin-top: 10px;
                padding-left: 10px;
                margin-bottom: -5px;

                & > li:last-child {
                    margin-bottom: 0;
                }
            }
        }
    }

    ::-webkit-scrollbar {
        width: 20px;
      }
      
      ::-webkit-scrollbar-track {
        background-color: transparent;
      }
      
      ::-webkit-scrollbar-thumb {
        background-color: #d6dee1;
        border-radius: 20px;
        border: 6px solid transparent;
        background-clip: content-box;
      }
      
      ::-webkit-scrollbar-thumb:hover {
        background-color: #a8bbbf;
      }
}

属实是提升幸福感的一个细节啊!


我实在太喜欢这个滚动条了,以至于我不希望它仅仅作用在目录上。

在我的博客中还有若干出现滚动条的地方,都是及其粗糙的浏览器原始样式:

无法忍受!

因此我决定直接将滚动条优化的代码拿出来,作用到 整个网站 上!没错,我希望使得访客在我的博客的任意一个角落都不会见到原始的、丑陋的滚动条!

简单粗暴地在 custom.scss 追加:

html{
    ::-webkit-scrollbar {
        width: 20px;
      }
      
      ::-webkit-scrollbar-track {
        background-color: transparent;
      }
      
      ::-webkit-scrollbar-thumb {
        background-color: #d6dee1;
        border-radius: 20px;
        border: 6px solid transparent;
        background-clip: content-box;
      }
      
      ::-webkit-scrollbar-thumb:hover {
        background-color: #a8bbbf;
      }
}

注意,为了使得代码简洁,我们把滚动条美化的部分拿出来之后,要回到TOC样式那边把相同的这些代码删掉。

现在,在我网站的所有地方,乃至于最外层的窗口右侧的滚动条都已经变成了优雅的样子 😏 。

注:希望读者使用的 不是 FireFox浏览器

相关文章组件美化

旧有的 相关文章 其实一直令我比较难受。JimmyCai大佬在设计文章列表的时候是考虑了每个文章配一个封面图的,因此在 相关文章 一栏也考虑了图片的存在,使得单个元素显得比较高大:

假如这些标题能够在垂直上居中,我倒也无所谓了,问题在于这些标题它居中不了……

因此我决定从几个方面来稍微改善一下视觉效果:

  • 在相关文章推荐的时候,多显示一些元素(不要只显示一个标题);
  • 不使用文章封面(本来也不打算每个文章都配封面)而是展示我们自行选定的图片。

依然是熟悉的开发者工具选中操作,在 layouts/partials/article/components/related-contents.html 找到 相关文章 组件的代码:

<aside class="related-contents--wrapper">
    {{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }}
    {{ with $related }}
        <h2 class="section-title">{{ T "article.relatedContents" }}</h2>
        <div class="related-contents">
            <div class="flex article-list--tile">
                {{ range . }}
                    {{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "articleList") }}
                {{ end }}
            </div>
        </div>
    {{ end }}
</aside>

从代码中可以看到,它使用的是 layouts/partials/article-list/tile.html 这个页面提供的文章列表,其代码为:

{{ $image := partialCached "helper/image" (dict "Context" .context "Type" .Type) .context.RelPermalink .Type }}
<article class="{{ if $image.exists }}has-image{{ end }}">
    <a href="{{ .context.RelPermalink }}">
        {{ if $image.exists }}  <!-- 必然为 false,因为我们的文章没配封面 -->
        	<div class="article-image">
			<!-- ==== 因此这部分代码不重要,略 ===== -->
            </div>
        {{ end }}

        <div class="article-details">
            <h2 class="article-title">
                {{- .context.Title -}}
            </h2>
        </div>
    </a>
</article>

根据我们的两点目标,我们应该:

  • 在下面一个 div 中添加 title 之外的元素,在这里我希望把文章的发表日期也呈现出来;
  • 绕过 image.exists 的逻辑,直接用作者的 div 包住自己选用的图片。

第一点非常简单,仿照 layouts/partials/article-list/compact.html 代码,在 h2 标签下添加一个 footer 即可;

第二点同样简单,此前已经仿照Avatar的代码写过返回顶部按钮,则在这里使用相同的办法添加一张图片即可。

修改过后的 tile.html 代码如下:

{{ $image := partialCached "helper/image" (dict "Context" .context "Type" .Type) .context.RelPermalink .Type }}
<article class="{{ if $image.exists }}has-image{{ end }}">
    <a href="{{ .context.RelPermalink }}">
        {{ if $image.exists }}  <!-- 必然为 false,因为我们的文章没配封面 -->
        	<div class="article-image">
			<!-- ==== 因此这部分代码不重要,略 ===== -->
            </div>
        {{ end }}
	
        <!-- 自行增加的图片 -->
        <div class="article-image">
            {{ $relatedImage := resources.Get ("img/related-content.png") }}
            <img src="{{ $relatedImage.RelPermalink }}" loading="lazy" 
            data-key="{{ .context.Slug }}" data-hash="{{ $image.permalink }}" 
            style="opacity: 0.3;"/>
        </div>
        
        <div class="article-details">
            <h2 class="article-title">
                {{- .context.Title -}}
            </h2>
            <!-- 自行增加的文章发布日期 -->
            <footer class="article-time">
                <time datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
                    {{- .context.Date.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
                </time>
            </footer>
        </div>
    </a>
</article>

选中样式美化

直接使用 好友的样式 ,在 custom.scss 中添加:

::selection {
    color: #fff;
    background: #557697;
}

必须夸一句,颜色选得不错 😆 。

Part 3 页面载入动画

这部分一定要单独拎出来!它实在是 SO COOL!

事情的由来是这样的:虽然我在几个月之前就给我的博客(在GitPage上)布置了 Netlify 的CDN,但考虑到大墙的影响,偶尔加载起来还是慢得吐血。

慢倒是无所谓,关键是点击完一个链接之后要加载老半天,页面也没什么反应,就跟宕机了一样,很尴尬的!

为此呢,我参考 望间 大佬的 这篇文章 ,为我的博客也搞了个页面载入动画。


补充:

我把页面部署上去之后,发现最终效果比较一般 😿

因为这个动画主要应对的是 页面加载了、但没完全加载 的场景,而我们的主要问题似乎是点击链接之后 连页面都加载不了 😿

Whatever,聊胜于无吧,大墙带来的问题看来是没那么容易平衡掉的……

Step 1 新增组件

这个加载动画的大致原理呢,就是在原本的比较大的页面加载完成之前,先加载出一些比较简单的元素,为这些元素制作一些简单的动效,这样访客就不会等得太无聊了!等原本的页面加载完成之后,再把这个组件隐匿掉即可!

因此,需要在原本的主题结构里面新增跟加载动画相关的组件。

我把这个小组件放在 layouts/partials/widget/preload.html 文件里,注意,这个文件是自己创建的:

{{ if .Site.Params.enablePreloadingAnim }}
        <div id="loading-box">
                <!-- 页面加载完成之后帷幕拉开的效果 -->
                <div class="loading-left-bg"></div>
                <div class="loading-right-bg"></div>
                <!-- 旋转盒子动效 -->
                <div class="spinner-box">
                        <div class="configure-border-1">
                                <div class="configure-core"></div>
                        </div>
                        <div class="configure-border-2">
                                <div class="configure-core"></div>
                        </div>
                        <div class="loading-word">加载中...</div>
                </div>
      </div>
      <!-- 页面加载动画 -->
      <script>
        $(document).ready(function () {
                document.getElementById('loading-box').classList.add("loaded")
        })
      </script>
{{ end }}

可以看到,这个文件里面有几个组成部分,非常简单:

  • Hugo的 if 判断句,便于我们在配置文件里控制这个组件要不要启用;
  • 用于呈现动效的几个简单的HTML元素;
  • jQuery的函数,直接使用 ready 函数,当页面载入完成之后把动效组件隐匿掉!

万事俱备,把这个组件直接加到网站模板里面就行了!Stack主题的网页模板在 layouts/_default/baseof.html ,我们找到 body 标签,在它底下加上这么一句:

<body class="{{ block `body-class` . }}{{ end }}">
    <!-- 下面这个是新加的 -->
    {{- partial "widget/preload" . -}}
    <!-- 下面就是Stack作者原本的代码了 -->
    {{- partial "head/colorScheme" . -}}
   	<!-- 省略省略省略 -->
</body>

一切就OK了!

Step 2 组件样式

我们刚才看到的jQuery函数非常简单,是因为这个动效最灵魂的地方是在组件的CSS啊!

找到老朋友 assets/scss/custom.scss ,在文件里面 追加

#loading-box .loading-left-bg,
#loading-box .loading-right-bg {
    position: fixed;
    z-index: 1000;
    width: 50%;
    height: 100%;
    // 我在这里小改了一下颜色,
    background-color: #b1c0c7;
    transition: all 0.5s;
}

#loading-box .loading-right-bg {
    right: 0;
}

#loading-box>.spinner-box {
    position: fixed;
    z-index: 1001;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100vh;
}

#loading-box .spinner-box .configure-border-1 {
    position: absolute;
    padding: 3px;
    width: 115px;
    height: 115px;
    background: #ffab91;
    animation: configure-clockwise 3s ease-in-out 0s infinite alternate;
}

#loading-box .spinner-box .configure-border-2 {
    left: -115px;
    padding: 3px;
    width: 115px;
    height: 115px;
    background: rgb(63, 249, 220);
    transform: rotate(45deg);
    animation: configure-xclockwise 3s ease-in-out 0s infinite alternate;
}

#loading-box .spinner-box .loading-word {
    position: absolute;
    color: #ffffff;
    // 我在这里小改了一下文字大小和字体,注意!
    font-size: 1.8rem;
    font-family: 'Zhi Mang Xing', cursive;
}

#loading-box .spinner-box .configure-core {
    width: 100%;
    height: 100%;
    background-color: #37474f;
}

div.loaded div.loading-left-bg {
    transform: translate(-100%, 0);
}

div.loaded div.loading-right-bg {
    transform: translate(100%, 0);
}

div.loaded div.spinner-box {
    // 你可以把这个注释掉,这样就能一直看那个动画的效果了!
    display: none !important;
}

@keyframes configure-clockwise {
    0% {
        transform: rotate(0);
    }

    25% {
        transform: rotate(90deg);
    }

    50% {
        transform: rotate(180deg);
    }

    75% {
        transform: rotate(270deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

@keyframes configure-xclockwise {
    0% {
        transform: rotate(45deg);
    }

    25% {
        transform: rotate(-45deg);
    }

    50% {
        transform: rotate(-135deg);
    }

    75% {
        transform: rotate(-225deg);
    }

    100% {
        transform: rotate(-315deg);
    }
}

OK,搞定!

在这里,我们没必要搞清楚动画原理,因为我感觉这个动画自定义的空间不大了。读者可以在网络上查找一下 CSS页面载入动画 这种相关内容,会发现其实已经有超级多设计师大牛把他们的作品开源了,我们没必要自己调整,总能找到一个很满意的啦!

Step 3 配置文件

完成上面两步之后,动画暂时还不能生效,因为我们新增组件的时候加了个 if 判断,还记得吗?

在配置文件的相应位置(我这里是 params 栏目底下)加上 enablePreloadingAnim: true 这个键值对,然后就可以享受这个很棒的载入动画了!

To Be Continued…

  • 文章页面微调(white-space: nowrap;目录项紧凑;……)
  • 主页侧边栏不亮
  • 文章添加返回最上层
  • 研究在线字体
  • 修改文章页面的 返回 按钮的跳转逻辑(不要直接回到主页,最好回到上一级)
  • 研究SVG动画,把加载动画优化成SVG绘制土楼!(福建人的执念啊)
  • 修复 archieves 页面顶部,categories用的 article-tile 的问题(之前改相关文章组件,把这个东西影响到了 😝 )
有朋自远方来,不亦说乎?
Built with Hugo
Theme Stack designed by Jimmy