博客建站记录(5)| 主题配置与修改(下):推翻重来的导航栏

Posted by 空谷 on 2017-10-15

其实上篇已经基本实现了目录的功能,优化一下内部样式就可以完事了。但是作为一个完美主义者,距离我想要的下滑自动跟随页面,自动定位文章在目录中的位置这样的功能还是相差甚远。

TOC Plugin For Bootstrap

在网上寻找解决对应的轮子时,找到一篇 文章 介绍了 Bootstrap 中的 Scrollspy 组件,震惊到原来其实 Bootstrap 已经提供了完美的解决方案,看来还是对 Bootstrap 不了解。之后还是要补一补。
而在该文章的第一个评论者说自己实现了给 Bootstrap 用的 TOC 插件。传送门 可以直接使用他做的插件来实现这个导航栏的功能。(需要使用 BootStrap 的 js 和 css 样式。)

根据他的教程,引入 Bootsrap 和TOC插件,成功实现了对应的锚点、定位等功能。主要是给 body添加 data-spy="scroll" data-target="#toc" 这一串属性,然后添加一个tocnav框架,添加上data-toggle="toc" data-spy="affix" class="affix">这一串必要的标识。

1
2
3
4
5
6
7
<body data-spy="scroll" data-target="#toc">
...
<div class="post-toc-wrapper">
<nav id="toc" data-toggle="toc" data-spy="affix" class="post-toc affix"></nav>
</div>
...
</body>

基于这个插件最终成功实现了锚点、定位等功能,但是目前存在诸多优化的问题。需要一个个解决。

TOC样式逻辑优化

经过测试发现,控制 TOC 样式的文件是 bootstrap-toc.min.css 而不是bootsrap.min.css,因此直接删除该文件,主要研究bootstrap-toc.min.css的样式特征。

作为一个主题来说,颜色应该做到统一,因此需要调用主题色,而单纯的 CSS 文件没法进行这样的调用,因此要使用 SASS 的方式进行设计。
重命名bootstrap-toc.min.csstoc.scss,并用 Chrome 格式化成可阅读模式,放入dev文件夹中。在app.scss中加入下列代码,使得 SASS 在生成 css 时也会把 toc.scss 这个文件加载进去。

1
@import 'toc';

$color-theme-default来替换所有的紫色色值。完成效果如下

目录布局调整

从修改逻辑上来说,应该先进行白天模式的修改,在此基础上再调整夜间模式的效果。因此将关掉夜间模式,对白天模式进行样式的调整。

目前的框架结构是这样的

1
2
3
4
5
6
7
8
9
<main class="post-container home-content">

<div class="post-toc-wrapper">
<nav id="toc" data-toggle="toc" class="post-toc"></nav>
</div>
<div class="post-article" >
...
...
</div>

我希望将目录移动到右边,那么作为基本容器的post-toc-wrapperpost-articleposition 必须要是 relative ,同时让post-toc-wrapperfloat值为right,如此一来,目录位置就会挪到右边。

接下来则是对固定跟随功能的设计。我希望达到的效果是:目录在页面刚滚动时随页面一起滚动,但到达某一位置后,它开始固定在页面上;页面滚动到评论部分时,目录不再固定,又随页面一起滚动。

这一个功能 其实Bootsrap 的 Affax 插件已经完全实现了。
从字面上翻译,则 affix 是固定的意义。这个插件里,这固定是有条件的。先来看下 affix 的效果,就是本篇右侧的目录导航。
在这边说一下应用的过程。

Affix 插件的使用

页面滚动过程中,Affix 插件会根据我们的配置参数切换应用到目录部分的 CSS 类,整个滚动过程会产生三个类:affix-topaffixaffix-bottom

插件提供的配置参数 offset: { },里面包括两个值:top 和 bottom。

整个过程用文字描述如下:

  1. 页面加载完毕后,应用 affix 效果的内容会增加一个 affix-top 样式类
  2. 当页面向下滚动了 top 的距离时,affix-top 切换成 affix
  3. 页面滚动到离底部距离为 bottom 时,affix 类切换成 affix-bottom

这样,我们根据需要定义这三个类的样式就好了。

因为前边已经引入了 JQuery 的库,这里就直接加入下列代码即可。

1
2
3
4
5
6
7
8
9
10
$('#toc').affix({
offset: {
top: function () {
return (this.top = $('.post-header').outerHeight() - 40 )
},
bottom: function () {
return (this.bottom = $('.author-detail').outerHeight() + $('.g-footer').outerHeight()+$('#toc').outerHeight())
}
}
});

这里我通过 JavaScript 设置 offset 值,而不是直接在 HTML 标签中应用属性 data-spy="affix"data-offset-topdata-offset-bottom,这是因为顶部和评论部分的高度无法确定,top 和 bottom 的值只能动态计算。

这样,页面加载完成后,#toc 有一个 affix-top 类,在滚动 top 值后,# toc 部分有一个 affix 类,在离页面底部距离 bottom 值时,#toc 部分的类又变成 affix-bottom
以下是 CSS 样式。

1
2
3
4
5
6
7
8
9
10
.affix{
position:fixed;
top:80px;
}
.affix-top{
position:absolute;
}
.affix-bottom{
position:absolute;
}

插件会自动计算 affix-bottomaffix-top 类的 top 值,所以无需多余的设置。

目录样式

position 为 fixed 时需要用 margin 来调整尺寸大小。
因为内部 a 自带 padding属性,所以去掉左右 padding 的值,只保留上下的。

1
2
3
4
5
6
7
8
9
10
11
.post-toc-wrapper {
float: right;
width: 280px;
}

.post-toc{
@include cardStyle;
padding:20px 0;
margin-right: 40px;
border: 0;
}

最后把字体大小调整为15px。

经过调整后的形式。

夜间模式同样效果良好

TOC 内容调整

因为默认生成的 TOC 带有标题,有标题的 TOC 实在看着奇怪,因此要把这个部分去掉。通过加入data-toc-skip即可实现。

1
2

<h1 data-toc-skip>{{ page.title }}</h1>

TOC宽度自适应

横排菜单

使用给 li 元素附加上 float:left 参数后就能实现目录栏的横排,但是这个时候显示并不对。

因为是 display:block的关系,所以post-toc的样式不会随内部元素的变化而变化。因此将其修改为display:flex,便能自动适应内部元素的尺寸。自适应良好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@media screen and (max-width: $post-container-w) {
.post-toc-wrapper {

//维持宽度
width: 960px;

//维持位置居中
margin:0 auto;
float: none;
}
.post-toc{
//增加与底部的间隙,维持内容居中
margin: 0 auto 10px;
//将 toc 的 padding 归0,用一个元素去控制
padding:0;

//使 toc 能包裹所有的内部元素
display: flex;

//增加阴影用于区分
box-shadow: 0 3px 9px rgba(0,37,55,.1);

//用于维持 Fixed 时的宽度
width: 960px;
}

nav ul {
//所有元素内容居中
margin: 0 auto;
//控制边距
padding:15px 10px;
//控制出现子集元素时父集的位置
text-align: center;
}

nav ul li {
//控制目录元素横排
float: left;
}


.affix-top{
//相对运动
position: relative;
}

.affix{
//置顶
top: 0;
//将目录放在顶层
z-index: 1;
}
}

下拉式二级目录

虽然说目前实现了所有的功能,但是次级目录的显示方式还是十分变扭。

之前看到 Bootstrap 的横向导航栏有下拉定位的方法,个人觉得十分不错,最终的目录样式应该像这个一样才对。

在这篇文章文章中找到几乎一样的设计思路:Responsive Dropdown Navigation Bar

再来观察一下 HTML 骨架。

1
2
3
4
5
6
7
8
9
10
11
12
13
<nav>
...
<ul class="nav">
....
<li><a href="#id3">id3</a>
<ul class="nav">
<li><a href="#id4">id4</a></li>
....
</ul>
</li>
....
</ul>
</nav>

SASS骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
nav {
ul.nav {
li {
a {
&:hover {
}
&:not(:only-child):after {
} }
// 下拉菜单部分
ul.nav {
li {
a {}
}}}}}

可以看到的是我们已经把父级菜单调整好了,需要二次调整的是子级菜单。
也就是nav .nav .nav的这些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
nav .nav .nav{
//控制成下拉菜单,而不是自由排布
position: absolute;
//将所有 padding 取消,使得元素和父级对齐
padding:0;
//使得水平线上和父级对齐。
margin-top:15px;
//下拉菜单的背景色
background-color: #fff;
//控制宽度,不然则会水平方向延伸
width: 150px;
text-align: left;
box-shadow: 0 4px 12px rgba(0,37,55,.1);
}

nav ul.nav .nav>li>a{
//重新设置子级元素的 padding 值
padding: 8px 20px;
}

nav ul.nav .nav>.active:focus>a, nav ul.nav .nav>.active:hover>a, nav ul.nav .nav>.active>a,nav ul.nav .nav>li>a:focus, nav ul.nav .nav>li>a:hover{
//针对所有的聚焦、悬浮、激活,都采取以下 padding 值
padding: 8px 10px;
}

实现效果:

因为 TOC 插件会将子级菜单持续显示,因此加入一条控制句柄,使得点击父级菜单可以收起对应的子级菜单。

1
2
3
4
5
6
7
8
(function($) {
$(function() {

$('nav[data-toggle=toc] .nav>li>a:not(:only-child)').click(function(e) {
$(this).siblings('nav[data-toggle=toc] .nav>.active>ul').toggle();
});
});
})(jQuery);

待优化部分

当然目前还存在一些问题。需要后面找时间解决了。

  1. 从阅读习惯上来说,顶部的目录栏的菜单应该默认收起,需要时再展开,而不是到一个区间就自动展开。
  2. 目前点击箭头和内容都是直接跳到父级标题的位置,希望优化成点击箭头展开子级菜单,点击父级标题跳转。
  3. 从上往下拉的时候目录栏还有位置的跳动,有待优化。

参考资料

  1. Implementing ScrollSpy with Jekyll to auto-build a table of contents
  2. Table of Contents plugin for Bootstrap
  3. Twitter bootstrap 的 affix.js 插件
  4. margin auto 实现居中,与text-align:center的区别
  5. text-align - CSS - MDN
  6. Responsive Dropdown Navigation Bar