自动生成文章目录TOC与定位导航
Synopsis: 要自动生成博客文章的目录 TOC(Table of Contents),可以在后端生成,比如 Markdown 有 TOC 插件。或者使用前端插件,比如 tocify、toc 等,同时前端插件还可以结合 scrollspy 等来实现定位导航,即向下滚动文章内容时,会自动定位到对应的目录项。本文使用前端 JS 生成目录,展示样式美观且能够定位导航,并支持 3 级目录
1. markdown toc
1.1 markdown.extensions.toc
我的博客使用 Flask
框架,实现文章 Markdown 的模块是 Python-Markdown
和 bleach
,所以我只需要启用 markdown.extensions.toc
扩展就能自动从 Markdown 原文中自动生成对应的 TOC
import markdown ... post = Post.objects.get_or_404(slug=slug) if post.content_raw: # content_raw 保存了文章的 Markdown 原文 md = markdown.Markdown( extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ], output_format='html5', encoding='utf-8') md.convert(post.content_raw) toc = md.toc else: toc = None
1.2 {{ toc|safe }}
然后在前端 Jinja2
模板中:
即可自动生成文章的目录了:
1.3 CSS 美化
但是现在的文章目录看起来不怎么美观,让我们修改一下,增加相应的 CSS
样式:
<style> .u-list-inline { padding-left: 0; margin-bottom: 0; list-style: none; } .g-ml-15 { margin-left: 1.07143rem !important; } .u-link-v5 { text-decoration: none; transition: all .2s; } .u-link-v5:hover, .u-link-v5:focus { text-decoration: none; } /* Color Aqua */ .g-color-aqua { color: #29d6e6; } .g-color-aqua--hover:hover { color: #29d6e6 !important; } /* Color Red */ .g-color-red { color: #f00 !important; } .g-color-red--hover:hover { color: #f00 !important; } </style>
提供 JavaScript
脚本当文档加载完成后,自动给 .toc
类的目录 div 应用相关的 CSS
样式:
<script> $(document).ready(function() { // 如果toc为空(只有最外层ul),隐藏整个TOC并返回 if ($('.toc ul').children().length <= 0) { $('.toc').parent().css('display', 'none'); return; } // 只显示1、2、3级目录列表 $('.toc ul li ul li ul li ul').css('display', 'none'); // 非默认的列表样式 $('.toc').find('ul').addClass('u-list-inline'); // 2、3级目录缩进 $('.toc ul li ul li').addClass('g-ml-15'); $('.toc ul li ul li ul li').addClass('g-ml-15'); // 链接颜色,鼠标悬停颜色 $('.toc').find('a').addClass('u-link-v5 g-color-aqua g-color-red--hover'); }); </script>
最终的效果图如下:
但是它不能跟随页面滚动时自动定位,而且,当你的目录子项很多时,目录将占用很长的纵向位置,不便于目录内容的展示
2. 前端生成 TOC
上面说的后端生成的 TOC 有一些缺陷,所以现在我们在前端通过 JS 来生成 TOC
注意: 请先自行安装
bootstrap
2.1 JS
创建 toc.js
文件,内容如下:
(function($) { "use strict"; window.Toc = { helpers: { // return all matching elements in the set, or their descendants findOrFilter: function($el, selector) { var $descendants = $el.find(selector); return $el .filter(selector) .add($descendants) .filter(":not([data-toc-skip])"); }, generateUniqueIdBase: function(el) { var text = $(el).text(); // Regex for finding the non-safe URL characters (many need escaping): & +$,:;=?@"#{}|^~[`%!'<>]./()*\ (newlines, tabs, backspace, & vertical tabs) var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\\n\t\b\v]/g, urlText; // Note: we trim hyphens after truncating because truncating can cause dangling hyphens. // Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." urlText = text .trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." .replace(/\'/gi, "") // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." .replace(nonsafeChars, "-") // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-" .replace(/-{2,}/g, "-") // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-" .substring(0, 64) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-" .replace(/^-+|-+$/gm, "") // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated" .toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated" return urlText || el.tagName.toLowerCase(); }, generateUniqueId: function(el) { var anchorBase = this.generateUniqueIdBase(el); for (var i = 0; ; i++) { var anchor = anchorBase; if (i > 0) { // add suffix anchor += "-" + i; } // check if ID already exists if (!document.getElementById(anchor)) { return anchor; } } }, generateAnchor: function(el) { if (el.id) { return el.id; } else { var
0 条评论
评论者的用户名
评论时间暂时还没有评论.