让你的 Jekyll 文章页阅读体验更好

前言

  Jekyll 与 Hexo 不同之处有很多,其中一处是在文章页面中不支持原生 [TOC] Markdown 语法来自动生成目录。而在 Hexo 中,即使主题不支持侧边悬浮的优化目录导航,也可以通过最简单的方式在文章的开始位置生成目录。虽然这种目录永远固定在文章开始的地方,但是总算是能够通过大小标题来给读者一个大概的思路。

Jekyll 生成目录的方案

  如参考资料 1 中所提到的,如果想要在 Jekyll 中实现文章目录,有三种不同的方案可供选择:

第一种方案

  利用完整的标签来生成静态目录,可以看到在本文的开头就是这样的一个实例。这种方法的好处是不需要修改什么复杂的模板或者添加什么样式,Github Pages 也默认支持这种方式。不好的地方是与标准的 Markdown 语法略有不同,而且每次都得在文章内容页面开头加上以下代码。如果你使用带有 markdownlint 插件的编辑器编辑文章时,可能会有一堆告警。当然,如果不想折腾的人,这种方法不失为一种最简单方便的解决方案。

* TOC
{:toc}

第二种方案

  利用第三方插件 jekyll-toc。这种方式在实现上比上一种要更加优雅一些,不需要自己修改或编写代码,只需要执行以下步骤即可。缺点在于 Github Pages 不支持这类自定义插件,你可能需要使用自定义的 workflow.yml 文件来指导 Github Action 来编译生成静态文件。如果不怎么了解 Github Action,恐怕这种方式部署在 Github Pages 上也不是很省心。

# gemfile

gem "jekyll-toc"

# 添加后需执行 bundle install 安装插件
# _config.yml

# 在全局配置文件中启用 jekyll-toc 插件
plugins: ["jekyll-toc"]

# 默认为所有文章启用 toc
defaults:
  - scope:
      path: ""
    values:
      toc: true

第三种方案

  采用新增 jekyll 模板的方式来支持自动生成目录。这种方式也可以直接运行在 Github Pages 下。主要的步骤是:

  • toc.html 文件下载到 _includes 目录下;
  • 在 _layouts 需要使用 toc 功能的页面模板的 content 前面加上 % include toc.html html=content %

实践

  从上述三种方案综合来看,第三种方案能够同时支持自动生成目录和 Github Pages,比较适合预期的需求。另外,采用模板的方式还有一个好处,可以在全局配置文件 _config.yml 中一键设置“开启”或“关闭”,配置上比较简单。但如果仅仅照搬上述的第三种方案,还是不能完全满足实际的需求。因为第三种方案的结果是自动生成目录的内容,并不涉及到具体的布局,也就是说只能放在某一个固定的位置。总结的来说,实际的目标起码需要满足以下两点:

  • 目标一:目录位于正文右侧(或左侧),且当内容滑动时目录位置固定不变。
  • 目标二:目录在宽屏时自动显示,在窄屏或移动端分辨率不足时自动隐藏。

布局

  从目标一来看,其实在大部分的静态博客主题中都是有这样的功能的(PS:可能 Jekyll 是个例外,原生只支持静态目录)。我们可以先来看两个例子 hexo-theme-evenmarkdownguide。前面的例子是 Hexo 主题,其中采用的是 js 控制 toc 的 div 层在 position: absoluteposition: fixed 之间变换:当页面初始时,处于 absolute 位置;当页面在向下滑动时,处于 fixed 位置。这种方式需要有 js 代码的介入,增加了运算的成本(虽然其实很小)和维护成本(占比更大)。后面的例子是采用了 Bootstrap 框架中的 toc js 插件,能够满足目标一,且能跟踪内容位置来切换显示的二级目录,相对来说功能更加强大。唯一的缺点是,拖着 Bootstrap 这个大拖油瓶,如果本身的主题是基于 Bootstrap 框架的,那么就非常合适了。

  这里我们想要实践的是在未使用 Bootstrap 框架的 Jekyll 主题中增加目标一的功能,因此这两个例子的做法都不是很合适。实际上,从需要维护的代码量来说,第一个例子的做法所需的 js 代码应该是算少的,但是不是存在完全不使用 js 代码也能实现这样的功能的方案呢?实际上是存在的。据我们所知,现在已有的页面布局的方法大致有三种:Table 布局Div 布局Flex 布局。Table 布局算是最原始的布局方式了,主要利用 Table 的横列来组织页面中的各个元素的位置,特点是容易上手且不易出问题。缺点也比较明显,不大符合语义化 HTML 的规范,即 HTML 标签只做与它含义相同的事情。Table 标签作为表格布局标签,应该专注于展示表格数据,而非为整个页面布局操心。于是,Div 布局开始流行起来。Div 层的概念和布局的含义完全吻合,也容易理解。Div 布局比较让人头疼的地方是,页面中有很多浮动元素出现时,可能会出现各种各样想象不到的问题,某些布局之后被迫要清除浮动。Flex 布局的出现为 Div 布局提出了改善,使得页面布局不再被浮动元素和 Div 层浮动时内容大小为零所困恼。

  由于想要目录内容块随着内容滑动而改变 position,我们可以采用 sticky 的 position 方案。这可能是 css 的一大进步,通过定义 sticky 的 position 可以让元素根据相邻元素的滑动而改变 position。其实,sticky 就等于 absolute 加上 fixed。只是这种等价只在某些条件成立时才能生效。我们可以通过参考资料 2~4 来了解更多详情。

目录生成

  目录生成这里直接采用的是上述的第三种方案。具体在 post 模板页使用 toc 模块的代码如最后所示。

自适应

  为了实现目标二,这里采用了最简单的 CSS 媒体查询,即在平常 PC 端宽屏时采用如下 common.sass 中的样式。显示目录时,正文内容宽度为 720 px,目录宽度为 280 px。同时为了将目录与正文拉大间距以及更好区分,这里增加了 margin-left (30 px)、border-left(2px)和 padding(10px)。总计为,720+280+30+10*2=1050 px(这里忽略了 2px 的边界)。不显示目录时,让正文占据所有宽度,并设置目录块为 display: none,即隐藏该元素。具体实现如最终代码 common.sass 和 layout.sass 所示。在没有 sass 编译环境下,此处的 sass 代码可以取出转换为 css 使用。

最终代码

<!-- post.html -->

{% if site.toc %}
  <div class="container">  
    <div class="contents">
      <article class="markdown-body">
        {{ page.content }}
      </article>
    </div>   
    <div class="table-of-contents">
      <h2>Contents</h2>
      {% include toc.html html=content %}
    </div>
  </div>
{% else %}
  <article class="markdown-body">
    {{ page.content }}
  </article>
{% endif %}
<!-- common.sass -->

.container {
    width: 1050px;
    margin: 0 auto;
    display: flex;
    flex-direction: row;
    .contents{
        width: 720px;
    }
    .table-of-contents{
        padding: 10px;
        border-left: 2px solid #efefef;
        width: 280px;
        position: -webkit-sticky;
        position: sticky;
        top: 80px;
        margin-bottom: 80px;
        height: fit-content;
        margin-left: 30px;
        h2 {
            font-family: fantasy;
            color: #e32e00;
        }
        ul{
            margin-left: 20px;
            list-style-type: revert;
            font-size: 14px;
            line-height: 24px;
            color: #005b81;
            a{
                color: #005b81;
                &:hover{
                    color: #e32e00;
                    text-decoration: underline;
                }
            }
        }
    }
}
<!-- layout.sass -->

@media screen and (max-width: 1050px) {
    .post-content {
        .container {
            width: 100%;
            .contents {
                width: 100%;
                float: none;
                margin: 0 auto;
            }
            .table-of-contents {
                display: none;
            }
        }
    }
}

参考资料

版权声明:如无特别声明,本文版权归 Mr Li 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 Jekyll 文章侧边索引导航 》

本文链接:https://lisz.me/tech/webmaster/jekyll-toc.html