写在前面的话
我这个人其实不太爱写博客。
上大学的时候,学习方法就是看书。先看教科书,教科书看完了,就去图书馆看书。当然,都是要边看边动手的。
书看的没劲了,就到网上找视频教程,生动形象。
后来安装的开源软件多了,就从软件中知道了软件的官网。进一步通过官方文档来学习。
书本、视频、官方文档,这些资料都有一个共同点:成体系又够权威。
不像博客,比较琐碎。
大部分的技术博客有以下问题:
- 只是记录一个问题的其中一个原因,以及对应的解决方法;
- 甚至是一个错误的解决方法,只是凑巧问题消失了;
- 再或者因为版本的问题,随着版本升级,方法已经不再奏效。
点居多,面太少,既不成体系,又不能保证正确性。
如果要把博客写得系统,就跟翻译没啥区别了,工程浩大,不如直接写书。面向问题呢,又如上面所说,分析的不全面,容易浪费读者时间,我也不愿为之。
但是只看不写也不是个好习惯。放在肚子里谁知道我会呢?不往外分享又怎么帮助人呢?
看的书多了,学习的内容多了。我逐渐相信:解决问题的思维比答案本身更重要,并深有感触。
所以我给我的博客的定位是多讲思维,多讲道理。
单纯地讲道理,或者以点带面地讲道理。
这类文章,我放在 明哥讲道 分类中。
下面先来说说这次我遇到的问题。
TOC是什么?
TOC 是 Table of Contents 的简称。翻译为中文就是目录。
目录大家肯定都知道,但是 TOC 就显得比较“高大上”,或者叫”难理解“。
这里给大家的一个建议,对于有明确含义的英文概念,一定要转换成中文去理解再记忆。这样在我们的思维中,整个信息的含义是自成一体的,不需要在脑海中去额外调用英文知识相关的区域再做一次翻译动作。
我在博文《新年新形象,网站换了新主题!》 中提到新主题的特点之一:
5.侧边栏显示文章目录结构。同样是一个很惊喜的功能。
这次说的问题就和这个功能有关。
最初遇到的问题
刚换为这个主题时,我就遇到了一个问题:文章页侧边栏目录处内容为空。
最初,发现这个问题后,我立刻打开主题 Demo 网站,使用浏览器开发者工具查看文章页内容的标题,发现是使用的 h1
标签。而我之前使用的主题,h1
字体非常大,所以我一般不用 h1
,从 h2
用起。按一般逻辑,我猜测是目录仅支持 h1
大标题,毕竟侧边栏空间有限。于是我对自己的文章内容做了修改,但本地预览后发现问题依旧。
如果让我来实现显示 Markdown 目录,正常来讲,应该是通过读取各个标题来实现。 h1
标题(即 #
)都不能显示,可以肯定这个主题并不是按这个路数实现。
一猜不中。
为了快速确定是否为文章内容的问题,根据控制变量法,我把 Demo 仓库里的博文 md 文件直接放到我的仓库里。本地预览发现可以显示目录内容!
可以确定,就是文章内容问题了!
查看 Demo 仓库的源码,可以很明显注意到在文章开头有以下内容:
* TOC
{:toc}
将这段代码加到我自己的文章开头,本地预览,侧边栏里的目录有内容了!
探寻实现机制
我知道这部分代码可以用来生成 Markdown 目录。
但是博客的文章页面,内容前面并没有目录啊!目录是在页面右上角的!
此时,脑中冒出了一些问题和猜测:
- 文章前的目录内容为什么没有显示?
- 右上角的目录内容和文章里的目录内容有什么关系?为什么加上代码,侧边栏里的目录就有内容了。难道是文章前的目录内容“跑”到右边去了?
首先探寻第一个问题,这个问题又可以分为两种情况:
- Jekyll 生成静态文件时并没有生成目录内容。直观看是这样的。
- Jekyll 生成静态文件时生成了目录内容,但是并隐藏了。
这个问题很好确定,打开开发者工具看一下就知道了。
可以看到,生成的 HTML 源码中有 markdown-toc
的内容,但是在 post.css
中,将这部分内容设置为了不显示。即上面所说的第二种情况。
第一个问题的答案确定了,第二个问题中我的猜测也八九不离十了。这样的设计,明显是为了“偷懒”:先利用已有的功能把目录内容生成,再把生成的内容复制一份显示再侧边栏,最后再把原始位置的内容隐藏掉!思路相当“鸡贼”,哦不,是巧妙!
看源码来验证一下猜测:
先看模版 /_layouts/post.html
源码:
<div class="content-navigation col-lg-3">
<div class="shadow-bottom-center" >
<div class="content-navigation-toc">
<div class="content-navigation-header">
<span class="octicon octicon-list-unordered"></span> 目录
</div>
<div class="content-navigation-list toc"></div>
</div>
<div class="content-navigation-tag">
<div class="content-navigation-header">
<span class="octicon octicon-list-unordered"></span> 标签
</div>
使用浏览器开发者工具可以看到 <div class="content-navigation-list toc"></div>
下面被填充了内容,并且和文章正文中被隐藏的目录内容一模一样。
这段代码上下没有相关代码,整个文件中也没有相关的 JavaScript 脚本,说明复制动作不是在这里定义的。
再看对应的 /static/js/post.js
源码,可以很容易看到以下内容:
/**
* 侧边目录
*/
function generateContent() {
var $mt = $('.toc');
var toc = $(".post ul#markdown-toc").clone().get(0);
$mt.each(function(i,o){
$(o).html(toc);
});
}
其中的 clone()
是那么地显眼。再看 var $mt = $('.toc');
这句,.toc
正是获取上面提到语句定义的类名。最后则是将文章正文的目录内容放置到侧边栏中。
实锤!
解决方法
理清了前因后果,百分百确定了原因。上面提到的解决方法也能确定的确是对症下药。即在所有文章的开头添加代码:
* TOC
{:toc}
遇到新问题
我还没有高兴多久, 又发现一个新问题:首页显示文章列表,文章摘要中会显示目录内容。类似下面这样:
多出了红色圈起来的地方,这可不是我想要的结果,太明显了。
并没有注意到主题 Demo 有这个问题,于是去看了下,发现也有这个问题……
只不过作者使用 Jekyll
的 Liquid Filters
表达式去除了 HTML 标签,使得摘要是以纯文本的形式显示的,多出来的目录内容显得不明显。
红线部分,就是多出来的目录内容。
改进解决方法
Liquid
过滤器可以去除 HTML 标签,可以去除空行,那有没有一个过滤器能去除前面几行的内容呢?
我查阅了 Liquid Filters
的官方文档 https://shopify.github.io/liquid/filters/abs/,尝试了几个有那么点可能性的过滤器,可惜都不行。
抛开问题,退一步,梳理整个流程:
- 使用代码在文章正文生成目录内容;
- 隐藏正文目录内容;
- 将正文目录内容显示到侧边栏目录处;
- 首页文章摘要截取文章前面部分内容,导致显示了正文前的目录内容。
1-3 都是必须的,这样侧边栏才能显示目录内容,这个功能我很喜欢,绝不能作废。
现在的问题就是,怎么解决 4。
post
中可以在头部单独添加 excerpt
字段来设置摘要内容,但是这种方式缺陷也很明显:
- 一般只能写一些简短的文字,类似微信公众号的文章摘要;
- 我也不想单独为每篇文章写摘要,截取文章前面一段就挺好的。
此时我想到。既然摘要是截取文章前面一段,那我把目录内容放摘要后面不是就可以了?反正正文里的目录内容被隐藏了,放在前面还是后面都是一样的。
尝试修改了一篇文章,果然可以:
- 首页摘要不再显示目录内容;
- 文章页正文也不显示目录内容;
- 文章页侧边栏目录内容正常显示。
所以解决办法就是:
<!--以上为摘要内容-->
* TOC
{:toc}
其中第一行为我在 _config.yml
中配置的摘要分割符:
excerpt_separator: <!--以上为摘要内容-->
又有新问题
这个问题本已告一段落。
没想到,过了几天,我偶尔用手机访问博客,发现又有新问题:在手机上访问时,目录内容会显示在放置 TOC 代码的位置。
也就是说穿插在正文中间,如下图所示:
体验实在是太不友好了。我作为一个重视用户体验的人,绝对不能接受。
再探实现机制
难道主题作者又换了路数?又“偷懒”,移动设备下直接用生成的正文里的目录内容,而不是使用响应式设计把侧边栏目录的内容移到正文前显示?
调出浏览器开发者工具一看,果然是:
@media screen and (max-width: 1199px)
.content-navigation {
display: none!important;
}
当屏幕分辨率宽度小于 1199px 时,侧边栏直接被隐藏。
再回过头来看正文中的目录内容的 CSS:
@media screen and (min-width: 1200px)
.post ul#markdown-toc {
display: none!important;
}
当屏幕分辨率宽度大于 1200px 时,不显示。换言之,当屏幕分辨率宽度小于 1200px 时,显示。
这也是响应式设计,作者的实现还是很巧妙。
再梳理整个流程:
- 使用代码在文章正文生成目录内容;
- 使用高分辨率设备时,将正文目录内容复制显示到侧边栏目录处,并隐藏正文目录内容;
- 使用低分辨率的移动设备时,隐藏侧边栏,正文目录内容不做处理,即直接显示。
- 将 TOC 代码放在摘要分隔符后面,首页摘要内容不含目录内容,高分辨率设备访问文章页也正常,但是使用低分辨率的移动设备访问时,目录内容的位置不正确。
最终解决方法
之前为了首页摘要能不显示目录内容,把 TOC 代码放后面了。现在想要移动设备的体验好,看来还必须放前面。
要让一个东西有不存在的效果,既删不掉,又挪不走。那就只剩一条路:让人看不到。
之前使用 Liquid Filters
表达式行不通,也考虑过不显示,但是发现首页生成的目录内容 HTML 片段和 文章页是一样的,因为是同样的 TOC 代码生成的嘛。
首页使用的是 style.css
来设置样式,这个是全局的,其他页面也引入来这个样式表,如果把首页的目录内容隐藏了,那文章页侧边栏的目录内容也会被隐藏。同时另一条“挪走”的路可行,完全满足要求,就没有再研究。
现在加上移动设备的显示问题,只能看有没有可能写样式将目录内容隐藏掉。
直接隐藏目录内容会导致文章页侧边栏的目录内容也被隐藏,那就也只有一条路:扩大范围来定位。
经过对比,最终找到了不同之处。
看正文中的目录内容的 CSS:
再来看一下高分辨率下隐藏正文中的目录内容的 CSS:
@media screen and (min-width: 1200px)
.post ul#markdown-toc {
display: none!important;
}
我最后在 style.css
中添加的 CSS:
section article ul#markdown-toc {
display: none;
}
写在后面的话
实际上,本文解决的并不是一个很大的问题,最终解决问题要写的代码也很少。如果按传统博客的学法,只要写上问题表现,再附上最后添加的几行代码就行了(本文有四五千字)。
但正如我在文章开头所说,解决问题的思维比答案本身更重要。
一个小数点,就可以引发一个大问题。要改正这个问题,也只需要修正一下小数点。然而真正的难点在于:我们怎么知道问题是由小数点的错误引起的?
怎样分析问题,怎样排查问题,看不到的脑中的思维活动,才是解决问题的关键。
我们看到的问题只是表象,内在的错误才是根因。
由表象推内在,这其实就是传统医学的“藏象思维”。
“藏象”即内在之藏与外应之象。这虽然是一个医学词汇,但传统医学的背后则是中华文化与中华哲学,是一种传统中国人看待问题、解决问题的思维方式。
物有物理,事有事理。
人体有人体的运行规则,程序也有程序的运行规则。
在寻找问题的解决方法上,并不一定要懂程序的具体语法。比如隐藏特定 HTML 元素的 CSS 代码具体怎么写,这是一个具体的知识点。但分析出要做隐藏这个事情,其中的思维过程,比如:
要让一个东西有不存在的效果,既删不掉,又挪不走。那就只剩一条路:让人看不到。
这就是一个完全与代码无关的事情。
程序比人体更简单,因为我们还能看到程序的源码,还能调试,这让我们做最后一步,即改正内在错误变得简单。现代医学的长足进步,就在于这一部分,通过解刨学,对人体内部结构有了更深入的认知。
培养思维能力,学习看待问题的独到视角,这是我认为的比知识更重要的事情(当然,知识也是必要的,但只有知识是万万不够的),也是我比较乐于分享的。
以后有机会,会专门写一写“藏象思维”。