如何用 Codemirror6 和 MarkedJS 实现双屏同步滚动

目录

本文收录在《如何开发一个自己的笔记软件》系列中,该系列源码均可在 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" })

最终效果如下

到此,一个双屏预览功能就完成啦,更完善的代码你都可以在源码地址中看到。

相关推荐
Henrii_历小海7 分钟前
WhatsApp Web 不是手机镜像:多设备协议、本地数据流与出海获客流水线
前端·智能手机
ayqy贾杰14 分钟前
Claude Fable 5 提示词泄漏,抓紧学习下
前端·后端·面试
To_OC8 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志9 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能