本博客的文章是基于markdown转化而成的。我的博客中大部分是技术文章,且有局部有比拟多的内容,所以这时候要是边上有一个题目导航栏就太美了。这篇文章用来记录实现题目导航栏的过程。
1. 基于 markdown 内容生成导航数据
要实现导航栏性能,首先要从文章中提取出题目信息。已知 html
中的题目是通过 h
标签实现,而 markdown
中的题目是通过 #
实现的。上面介绍导航数据提取的摸索和实现过程。
1.1 实现形式一:正则提取 markdown
已知 markdown 中题目的编码方式是 #
号 + 空格 + 题目名称,而有几个 #
号就代表几级题目。所以第一计划的要害办法就是依据这一特色对 markdown
内容进行提取。写完题目个别会用换行符结尾,咱们能够生成这么一个正则来对题目进行提取:
const regHeader = /(#{1,5})\s.*\n/g // 只提取 h1-h5
咱们须要的要害信息是 第几级题目 和 题目内容。基于这个指标咱们应用正则从markdown编码中提取出一个题目的数组
const regHeader = /(#{1,5})\s.*\n/g // 题目正则const headerArr = contentMd.match(regHeader)const showSideNav = headerArr && headerArr.lengthconst navArray = !showSideNav ? [] : headerArr.map(item => ({ content: item.replace(/(#{1,5}\s)|(\n)/g, ''), // 题目内容 headerIndex: item.match(/#{1,5}/)[0].length // 几级题目 }))
1.1.1 有余剖析
这种办法有个无法弥补的劣势,如果将 #
写在 code 中
```# 题目1```
咱们心愿失去的 # 题目1
是个纯文本,在这里并没有生成题目的想法,然而正则还是会将这个题目提出来。
1.2. 基于 marked.js 库中自定义 render 提取导航数据
在查阅材料和翻看 marked.js 文档的时候,我发现 marked.js 能够自定义 render 办法。那只有在 render 办法中保留题目信息就能够提取出导航数据了。
在这里咱们要保留两个数据:
- 导航栏列表
navList
,外面蕴含三个字段
navList: [ { level: 1, // 题目层级,h1,h2,h3,h4 no: 1, // 同一层级下的序号,在该层级题目下的排序 text: '题目1' // 题目内容 }]
- 题目层级对象
navIndexObj
这个对象用来记录以后题目渲染时已有该层级的题目数量,每次 render 题目的时候记录该层级题目的数量 + 1
对应的 marked.js
render 函数
// 博客应用 react 实现,所以将这两个数据放到 redux 中保留import marked from 'marked'import hljs from './highlight'import store from '../store'import { actionCreators } from '../views/detail/store'const renderer = new marked.Renderer()renderer.heading = (text, level) => { if (window.location.href.includes('/detail/')) { // 详情页提取标题栏 store.dispatch(actionCreators.addNavIndex(level)) // 该层级题目数量 + 1 store.dispatch(actionCreators.addNavList(level, text)) // navList 列表新增一个数据 const index = store.getState().getIn(['detail', 'navInfo', 'navIndexObj', level]).length + 1 return ` <h${level} id="h${level}-${index}" class="blog-detail-header" data-link="linkToh${level}${index}">${text}</h${level}> ` } else { return ` <h${level}>${text}</h${level}> ` }}marked.setOptions({ renderer, highlight: (code) => { return hljs.highlightAuto(code).value }, pedantic: false, gfm: true, tables: true, breaks: true, headerIds: false, sanitize: false, smartLists: true, smartypants: false, xhtml: false})export default marked
对应的 reducer
代码
import { fromJS } from 'immutable'const initialState = fromJS({ navInfo: { navIndexObj: {}, navList: [] }})export default (state = initialState, action) => { switch (action.type) { case ADD_NAV_INDEX: return state.updateIn(['navInfo', 'navIndexObj', action.data], item => item ? new Array(item.length + 1) : []) case ADD_NAV_LIST: return state.updateIn(['navInfo', 'navList'], item => item.push({ level: action.level, no: state.getIn(['navInfo', 'navIndexObj', action.level]).length + 1, text: action.text })) default: return state }})
导航栏组件就依据 navList
数据生成即可。
具体代码能够参考 博客源码。
有余剖析
该计划没有显著毛病,选定为最终计划。
2. 实现题目和导航栏联动
上文咱们曾经提取出了文章的导航栏列表数据。上面咱们通过这些数据实现题目和导航栏联动的性能。
2.1 点击导航栏跳转到文章题目
咱们在用 marked.js
渲染题目的时候,曾经依据题目的层级和程序给题目生成了一个惟一的id。咱们依赖 navList
生成导航栏,咱们也能够依据这个数据轻易地拼出各条导航栏对应的题目的 id。找到 id 后通过 element.scrollIntoView()
让元素跳转到视图中即可。
2.2 滚动文章时题目对应的导航高亮
咱们接下来实现另一个联动性能:在浏览文章时,当题目滚动到可视区域时对应的题目导航栏也滚动到可视区域且高亮。
JS有一个用来察看元素和窗体相交状态的观察器 IntersectionObserver,咱们这里用它来实现文章关联标题栏的性能(不思考兼容性)。
观察器相干代码如下
const scrollObserve = () => { const navObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { activeNav(entry.target) // 高亮观察器对应的导航菜单 } }) }) document.querySelectorAll('.blog-detail-header').forEach(ele => { // 对所有的题目绑定观察器 navObserver.observe(ele) }) }
具体性能代码能够参照源码。
最终实现成果
最终根本实现了题目和导航栏联动的性能。
完结撒花,新年快乐????????????
参考
应用 marked 解析 Markdown 并生成目录导航 TOC 性能
The renderer
尝试应用JS IntersectionObserver让题目和导航联动