如何用 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" })

最终效果如下

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

相关推荐
开心工作室_kaic16 分钟前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿35 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具1 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript