目录
本文收录在《如何开发一个自己的笔记软件》系列中,该系列源码均可在 Blossom 笔记软件 仓库中查看。仓库地址:
如何用 Codemirror6 和 MarkedJS 实现双屏同步滚动
在使用 Markdown 的过程中,经常遇到编辑区域和预览区域在滚动时不同步的问题,本文介绍了一种解决方法,可以一定程度解决该问题。
一般来说,双屏滚动不同步是因为滚动的距离是通过滚动条距离页面顶部的百分比来计算的。但是两个屏幕的高度不是统一的,例如,一张图片的 Markdown 内容只有简单的一行,但如果这张图片高度十分高,那么势必会影响到双屏的同步滚动。
less
// 这是一张高度很高的图片,会占用很大的预览空间
![图片](https://w.wallhaven.cc/full/9m/wallhaven-9m2w3x.png)
所以一种方法是,知道当前编辑部分最顶部显示的内容,然后在预览框中也显示相同的内容。比方说当前编辑框顶部是整个文本的第 10 行,那么预览框中就显示这一行 markdown 渲染后的内容。
如果是自己从头实现编辑器,那么进行这样的拓展可能很简单。但是如果是基于 codemirror 需要如何做呢?毕竟我们很难将编辑的每一行和预览的每一行进行一一对应。
但在翻看 codemirror 的 API 时,发现 codemirror 提供了获取当前文档距离浏览器顶部的距离的属性
typescript
editor.documentTop
当编辑器滚动条在顶部时,如下图,获取的值即为0
此时我们将滚动条向下滚动到下图所示位置时,将会获取到一个负值,这个负值是已经超出屏幕的文档顶部,距离浏览器顶部的距离。
通过这个值,我们可以获取文档顶部到当前浏览器顶部的文档内容。
typescript
// 不可见那部分 markdown
const invisibleMarkdown = editor.state.sliceDoc(0, editor.documentTop)
既然这部分 markdown 不可见了,那么相应的,这部分渲染后的 html 肯定也不能在预览窗口中看到,如下图
那么如何将对应的 html 内容隐藏起来呢?既然我们已经获取到了隐藏部分的 markdown,那么我们可以先把这部分转换成 html
typescript
// 不可见那部分 markdown
const invisibleMarkdown = editor.state.sliceDoc(0, editor.documentTop)
// 不可见部分的 html
const invisibleHtml = marked.parse(invisibleMarkdown)
接着把这部分转换成 dom 元素
typescript
// 匹配的元素
const matchHtmlTags = 'p, h1, h2, h3, h4, h5, h6, ul, ol, li, pre, blockquote, hr, table, tr, iframe, span'
// 不可见那部分 markdown
const invisibleMarkdown = editor.state.sliceDoc(0, editor.documentTop)
// 不可见部分的 html,如果你使用其他 markdown 解析工具也是同样的
const invisibleHtml = marked.parse(invisibleMarkdown)
// 将不可见的的 html 转换为 dom 对象, 是一个从 <html> 标签开始的 dom 对象
const invisibleDomAll = new DOMParser().parseFromString(invisibleHtml, 'text/html')
// 从这个 dom 对象下, 寻找一系列标签, 获取这些标签集合
const editorDoms = invisibleDomAll.body.querySelectorAll(matchHtmlTags)
通过上面的代码,已经获取到隐藏起来的 html 的 dom 元素有多少个editorDoms.length
,那么这个长度,也就是需要隐藏起来的 dom 元素的长度了。那接下来我们只要定位到预览窗口中,这个位置下的 dom 元素,就可以轻松的将滚动条移动到该位置。
typescript
// 匹配的元素
const matchHtmlTags = 'p, h1, h2, h3, h4, h5, h6, ul, ol, li, pre, blockquote, hr, table, tr, iframe, span'
// 不可见那部分 markdown
const invisibleMarkdown = editor.state.sliceDoc(0, editor.documentTop)
// 不可见部分的 html,如果你使用其他 markdown 解析工具也是同样的
const invisibleHtml = marked.parse(invisibleMarkdown)
// 将不可见的的 html 转换为 dom 对象, 是一个从 <html> 标签开始的 dom 对象
const invisibleDomAll = new DOMParser().parseFromString(invisibleHtml, 'text/html')
// 从这个 dom 对象下, 寻找一系列标签, 获取这些标签集合
const editorDoms = invisibleDomAll.body.querySelectorAll(matchHtmlTags)
// 从预览页面下同样寻找相同的元素
const previewDoms = this._preview.querySelectorAll(matchHtmlTags)
// dom 集合中,下标位置 与 不可见的 dom 元素的总数相同的,就是应该在预览窗口中显示的那个内容
const tagetDom = previewDoms[editorDoms.length]
tagetDom.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" })
最终效果如下
到此,一个双屏预览功能就完成啦,更完善的代码你都可以在源码地址中看到。