背景
众所周知 React
文档一言难尽,那属于是谁看谁知道了。且不说文档内容和组织如何,那不是咱能改变的,咱也没那个实力去评价。有个关键问题是:没有文档导航目录,导致有些长文档只看滚动条都能把人(我)劝退。
React现役文档
没有目录的长文档容易把人劝退的原因有哪些呢?
-
内容结构不直观 一般文档内容高度超过一屏,就没法一眼看出文档的大体结构和内容
-
导航不方便 点进某个文档想先看看结论怎么办,疯狂拉扯滚动条;看到结尾想回到顶部怎么办,疯狂拉扯滚动条;文档引用前面或后面某个主题怎么办,疯狂拉扯滚动条
-
容易忘记主题 一些比较长的段落,特别是有大段代码那些,可能看着看着就忘记这一段的主题
-
查找内容不方便 有时候明明记得有些东西在文档里看过,回去找的时候就是找不到(能搜到的另说),主要还是前两点引起的
React新版文档
相比之下新版文档体验就好很多,左侧是章节目录右侧是文档目录,内容结构清晰很多,导航也很方便,目录里面的标题都可以点击跳转。
React新版文档
虽然新版文档好很多,但距离完成应该还要些时间,目前进度是:Learn React 70%,API Reference 20%。想看中文版得更久。。。
如何解决
咋解决,这还不简单,给他写个文档目录就完事了!没错,当时我心里就是这么想的!(往往产品锦鲤听到这话都会蜜汁微笑、笑而不语。。。)
没用多久,我就写出下面3行代码
const linksHtml = [...document.querySelectorAll('h2,h3')].map(n => `<p style="padding:10px;"><a href="#${n.id}">${n.textContent}</a></p>`).join('');
const containerStyle = 'position: fixed;width: 260px;max-height:calc(100vh - 100px);top: 80px;right: 20px;font-size: 14px;border-radius: 5px;background: #fff;overflow-y:auto;';
document.body.insertAdjacentHTML('beforeend', `<div style="${containerStyle}">${linksHtml}</div>`);
- 第一行代码是提取文章中的标题节点(标签名为
h2
或h3
),并一一生成导航链接(<a>
标签节点),最后拼接成html
串linksHtml
- 第二行代码是声明导航目录容器的样式
- 第三行代码是将
linksHtml
内嵌到导航目录容器的html
,再把整串html
追加到document.body
末尾
在浏览器控制台运行看看效果
功能基本满足需求,有目录大纲也能导航跳转,看着还行哦~
如何使用
代码写好之后平常怎么使用呢?可以借助 DevTools
的 Snippets
存储代码,需要的时候手工执行。如何用 Snippets
执行代码可以看下面这张图,更详细的说明可以查看Edge文档。
如何易用
把代码做成 snippet
只是能用,但每次新打开文档页面都得手工执行这段代码,是不是有点麻烦?能做成自动的吗?
当然能!只要把上述代码自动注入到 React
文档网站的 JS
文件里面就OK啦!
由于服务器不是咱们的,想动服务器是不可能了!除非你有高超的黑客技术,还有不怕劳改的觉悟!实际上我们只需要篡改本地的 React
文档页面就行,因此,可以从下述4个方向考虑:
- 1、使用抓包工具(Fiddler、Charles)拦截请求,篡改静态资源文件,将代码写入
- 2、借助已有插件,通过插件操作
DOM
,将代码写入页面(Custom Style Script) - 3、自己写个插件,插件的
content scripts
会被注入到页面并自动执行 - 4、借助
DevTools
的Local Overrides
功能,使用本地文件覆盖线上文件
浏览器插件也能拦截http请求和响应,理论上也能篡改静态资源文件,只是常规拦截方式无法直接获取和修改响应内容,需要借助 DevTools
实现,略为复杂,篇幅有限这里不做深入探讨。本文咱们只介绍 Local Overrides
方法。
Local Overrides
通常可以用来调试线上代码,这里算是一个实际例子!
Local Overrides
使用 Local Overrides
功能覆盖线上文件的具体步骤如下:
-
1、找到
Overrides
面板,点击Select forlder for overrides
-
2、选择任意文件夹,用于存储要覆盖的文件,这里选了名为
test
的目录 -
3、允许弹出的权限提示
-
4、至此
test
文件夹就已经准备就绪,可以用于存储要覆盖的文件 -
5、切换到
Page
面板,选择任意JS
文件进行覆盖,这里我们选择app-e6edaaf1f610273f88f3.js
这个文件 -
6、往选择的
JS
文件写入代码 -
7、刷新页面看看效果
搞定!之后每次进页面或刷新页面,右侧的导航目录都会自动渲染!
这里一番折腾,实际只是想使用本地的 app-e6edaaf1f610273f88f3.js
文件覆盖线上的同名文件,然后通过修改本地文件达到篡改页面逻辑的目的。
Local Overrides
的详细介绍可以查看Edge文档
兼容SPA
本以为可以到此结束,没想到使用一会后又出现新的问题!由于 React
文档网站是个单页面应用(SPA
),切换文章时不会刷新页面,因此导航目录不会自动更新!比如从 API REFERENCE
下的 React
切换到 ReactDOM
这篇文章,目录不会自动更新。
这里有4个思路可以解决问题:
- 1、切换文章的时候
document.title
会变化,可以用MutationObserver
监听title
变化,在回调函数中重新生成目录或刷新页面 - 2、重写
history.pushState
和history.replaceState
,另外还要监听popstate
事件,在历史记录变化时重新生成目录或刷新页面 - 3、事件代理监听文章标题的点击事件,在回调函数中重新生成目录或刷新页面
- 4、
setInterval
轮询检查页面链接,链接变化时重新生成目录或刷新页面
这里只用方法1监听 title
变化实现,话不多说直接上代码:
title
变化时刷新页面,只需添加下述2行代码
const target = document.querySelector('title');
new MutationObserver(() => location.reload()).observe(target, {childList: true});
title
变化时重新生成目录的完整代码如下
function renderToc() {
const linksHtml = [...document.querySelectorAll('h2,h3')].map(n => `<p style="padding:10px;"><a href="#${n.id}">${n.textContent}</a></p>`).join('');
const containerStyle = 'position: fixed;width: 260px;max-height:calc(100vh - 100px);top: 80px;right: 20px;font-size: 14px;border-radius: 5px;background: #fff;overflow-y:auto;';
document.body.insertAdjacentHTML('beforeend', `<div style="${containerStyle}">${linksHtml}</div>`);
return document.body.lastElementChild;
}
let tocNode = renderToc();
const target = document.querySelector('title');
new MutationObserver(() => {
if (tocNode) tocNode.remove();
tocNode = renderToc();
}).observe(target, {
childList: true
});
注意:实现过程中发现,在React文档页面使用方法2重写
pushState
和replaceState
的操作不能永久生效,切换到其他文章后重写的方法会被还原
调试之后发现可以通过重写gatsby
的navigation.js
中声明的全局函数window.___navigate
达到目的,猜测被还原的原因也可能与此有关
具体原因留给有兴趣的小伙伴去探究,有结果可以在评论区告诉我
至此,咱们算是完成了一个基本可用的导航目录 Demo
,还有很多视觉和交互问题需要改进,比如:标题链接没有高亮、标题链接没有分层级缩进、标题过长出现横向滚动条等等。进一步的优化会在下一篇文章中细说,如果有兴趣也可以直接体验完成版 OneToc
浏览器插件。
OneToc
OneToc
(Toc
是 Table Of Content
的缩写)的主要功能是为技术文档、技术博客等网站添加导航目录,提供更好的阅读体验。代码仓库地址是:github.com/Whilconn/on…
使用效果图
欢迎大家安装体验以及反馈提问!