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

最终效果如下

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

相关推荐
Younglina4 分钟前
一个纯前端的网站集合管理工具
前端·vue.js·chrome
木头程序员5 分钟前
前端(包含HTML/JavaScript/DOM/BOM/jQuery)基础-暴力复习篇
开发语言·前端·javascript·ecmascript·es6·jquery·html5
卖火箭的小男孩7 分钟前
# Flutter Provider 状态管理完全指南
前端
小雨青年8 分钟前
鸿蒙 HarmonyOS 6|ArkUI(01):从框架认知到项目骨架
前端
Null15510 分钟前
浏览器唤起本地桌面应用(基础版)
前端·浏览器
pas13625 分钟前
31-mini-vue 更新element的children
前端·javascript·vue.js
wordbaby29 分钟前
TanStack Router 实战:如何构建经典的“左侧菜单 + 右侧内容”后台布局
前端·react.js
oMcLin44 分钟前
如何在 RHEL 7 上优化 Nginx 与 PHP‑FPM 配置,确保高并发 Web 应用的稳定性与响应速度?
前端·nginx·php
Taiyuuki1 小时前
WebGPU 开发者福音!在 VS Code 中实时预览你的WGSL着色器作品
前端·gpu·图形学
李剑一1 小时前
uni-app实现网络离线定位
前端·trae