由于一直在使用 markdown 编辑器写技术文章,所以对于编写体验很敏感。我发现各大社区的 markdown 编辑器基本都有同步滚动功能。只不过有些做得好,有些做得马马虎虎。出于好奇,我就打算自己亲自实现一下这个功能。

思考了一段时间,最后想出来了三种方案:

  1. 百分比滚动
  2. 双屏同时渲染占用面积大的元素
  3. 每一行的元素都赋上一个索引,根据索引来精确同步每一行的滚动高度

百分比滚动

假设现在正在滚动 a 屏,那 a 屏的滚动百分比计算方式为:a 屏的滚动高度 / a 屏的内容总高度,用代码表示 a.scrollTop / a.scrollHeight。当滚动 a 屏时,需要手动同步 b 屏的滚动高度,也就是根据 a 屏的滚动百分比算出 b 屏的滚动高度:

a.onscroll = () => {b.scrollTo({ top: a.scrollTop / a.scrollHeight * b.scrollHeight })
}

原理就是这么简单,可惜实现效果不太好。

从上面的动图可以看出,当我在第二个大标题处停留的时候,左右双屏的内容是同步的。但当我滚动到第三个大标题时,左右双屏的内容高度已经差了将近 300 像素了。所以说这个方案勉勉强强能用吧,聊胜于无。

双屏同时渲染占用面积大的元素

双屏内容高度不一致,是因为 markdown 同一个元素渲染后的高度和渲染前会有差别。例如一个图片,用 markdown 写就一行代码的事,但渲染出来的图片有大有小,高度几十、几百像素的都有。如果 markdown 的图片代码双屏同时渲染,倒是能解决这个问题。

但是除了图片仍然有不少元素渲染前后的高度是有差距的,虽然没有图片这么夸张。譬如 h1 h2 这种,当文章内容越长,这种小差异带来的问题会越来越大,导致双屏内容高度的差距也会越来越大。所以说这种方案也不是很靠谱。

每一行的元素都赋上一个索引,根据索引来精确精确同步每一行的滚动高度

之前两个方案都属于勉强能用,不够好。现在这个第三方案就比前面两个强多了,几乎能做到精确同步每一行的内容。具体怎么做呢?

第一步,监听 markdown 编辑框的内容变化,为每一个元素赋上一个索引,空行空文本除外。

当把编辑框的 HTML 传给右边的框渲染时,需要把 data-index 赋值给渲染后的元素。这样就能通过 data-index 精确定位渲染前后的同一元素了。

第二步,根据 a 屏的元素滚动高度计算 b 屏上同一索引的元素滚动高度

在 a 屏进行滚动时,需要从上到下遍历 a 屏的所有元素,并且找到第一个在屏幕内的元素。找到第一个在屏幕内的元素 这句话的意思是因为在滚动过程中,有些元素会因为滚动跑到屏幕外面(原来在屏幕内,滚动到屏幕外),这些元素我们是不需要计算的。

判断一个元素是否在屏幕内:

// dom 是否在屏幕内
function isInScreen(dom) {const { top, bottom } = dom.getBoundingClientRect()return bottom >= 0 && top < window.innerHeight
}

除了判断元素是否在屏幕内,还需要判断这个元素在屏幕内的部分占整个元素高度的百分比。譬如说一个图片的 markdown 字符串,由于滚动的原因,导致一半在屏幕内,一半在屏幕外。为了精确同步,那么渲染后的图片也必须有一半在屏幕内一半在屏幕外。


计算元素在屏幕内的百分比代码:

// dom 在当前屏幕展示内容的百分比
function percentOfdomInScreen(dom) {// 已经通过另一个函数 isInScreen() 确定了这个 dom 在屏幕内,所以只需要计算它在屏幕内的百分比,而不需要考虑它是否在屏幕外const { height, bottom } = dom.getBoundingClientRect()if (bottom <= 0) return 0 // 不在屏幕内if (bottom >= height) return 1 // 完全在屏幕内return bottom / height // 部分在屏幕内
}

现在我们就可以从上到下遍历 a 屏的所有元素,找到第一个在屏幕内的元素了:

// scrollContainer 即上面说的 a 屏,ShowContainer 是 b 屏
const nodes = Array.from(scrollContainer.children)
for (const node of nodes) {// 从上往下遍历,找到第一个在屏幕内的元素if (isInScreen(node) && percentOfdomInScreen(node) >= 0) {const index = node.dataset.index// 根据滚动元素的索引,找到它在渲染框中对应的元素const dom = ShowContainer.querySelector(`[data-index="${index}"]`)// 获取滚动元素在 a 屏中展示的内容百分比const percent = percentOfdomInScreen(node)// 计算这个对等元素在 b 屏中距离容器顶部的高度const heightToTop = getHeightToTop(dom)// 根据 percent 算出对等元素在 b 屏中需要隐藏的高度const domNeedHideHeight = dom.offsetHeight * (1 - percent)// scrollTo({ top: heightToTop }) 会把对等元素滚动到在 b 屏中恰好完全展示整个元素的位置// 然后再滚动它需要隐藏的高度 domNeedHideHeight,组合起来就是 scrollTo({ top: heightToTop + domNeedHideHeight })ShowContainer.scrollTo({ top: heightToTop + domNeedHideHeight })break}
}

从动图来看,目前已经做到行内容的精确同步了。

踩坑

有一些元素渲染后会变成嵌套元素,例如表格 table,渲染后的内容层级为:

<table><tbody><tr><td></td></tr></tbody>
</table>

按照目前的渲染逻辑,假如我写了个表格:

|1|b|
...

那么 |1|b| 上的 data-index 会对应到 table 上。

那这就会有个 bug,当 |1|b| 滚动到 50% 的时候,整个 table 也会滚动到 50%。这个现象如下图所示:

这和我们相要的效果不一样。a 屏连一行的内容都没滚完,b 屏整个内容已经滚动到一半了。

所以像这种嵌套的元素,在打 data-index 标记时,要把它打到真正的内容上。用表格 table 来做示例,就得把 data-index 的标记打在 tr 上。

这样一来,同步滚动就正常了。同理,其他的嵌套元素也一样(譬如 ul ol)。

总结

完整的代码我已经放在 github 上了:

  • markdown-editor-sync-scroll-demo

还有在线 DEMO:

  • demo1
  • demo2
  • demo3
  • demo4
  • demo5
  • demo6

如果在线 DEMO 比较慢,可以克隆项目后直接打开 html 文件访问。

markdown 编辑器实现双屏同步滚动相关推荐

  1. Markdown编辑器的使用方法

    本文目录 Markdown介绍 快捷键 1.标题 2.粗体/斜体/删除线/字体底色 3.上标/下标 4.无序排列 5.有序排列 6.待办事项 7.引用 8.脚注 9.自动注释 10.链接  a) 地址 ...

  2. Atom-无懈可击的Markdown编辑器

    备战美赛期间,向岳神学习,搞了Atom玩协作开发,第一次没有自动补全的手撸了遗传算法.今天发现Atom还有写Markdown的妙用,遂拿来练手. 1. 安装Atom 下载安装Atom:https:// ...

  3. Typora markdown公式换行等号对齐_下了31个markdown编辑器,我就不信选不出一个好用的...

    markdown编辑器测评 标准 总体标准 渲染领域 编辑领域 数据管理 其他 Typora Vnote Mweb Joplin Zettlr macdown ulysses Marktext gho ...

  4. 使用Atom快速打造好用的Markdown编辑器

    使用Atom快速打造好用的Markdown编辑器 Atom当前主流的跨平台的三大编辑器(Atom,sublime,vscode)之一 今天尝试了使用Atom来打造Markdown编辑器,快速上手且易用 ...

  5. 在线编辑_水墨-在线 Markdown 编辑器

    水墨-在线 Markdown 编辑器 基于 Spring-boot.FreeMarker.layui.Vditor 构建的一款在线 所见即所得的 Markdown 编辑器.水墨-在线 Markdown ...

  6. markdown 编辑器_推荐一款公众号 Markdown 编辑器

    公众号 Markdown 编辑器 简介 这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,只需将 MD 文档复制到左侧栏,再在右侧栏顶部"点击复制",右侧预览内容就 ...

  7. mac markdown_适用于Mac的最佳Markdown编辑器

    mac markdown Markdown provides a convenient way to add formatting to a plain text document, while le ...

  8. Gitbook详解(七)-Markdown编辑器推荐

    文章目录 1. 编辑器类型 2. MarkdownPad 3. Typora 4. Mou 5. Atom 6. Haroopad 7. Cmd Markdown 8. 最后 1. 编辑器类型 Git ...

  9. Typora收费了?推荐两款Markdown编辑器

    大家好,这里是扫地工. 这是一篇迟了两个月的推文,Typora 在11月底时推出正式版本,并正式转为收费版本. 这个话题当时直接冲上了知乎热榜第一,不过我很少使用Markdown,而且一直觉得Typo ...

最新文章

  1. summary_1.正念自控法
  2. 他在 B 站有 140 万粉丝,今天来免费带你学 Linux 了!
  3. python 打包发布网站_Python代码的打包与发布
  4. python dry原则_关于Python 的这几个技巧,你应该知道
  5. Hive 之 常用函数
  6. hector与gmapping总结
  7. C语言-字符数组和字符串
  8. win10最常用dos命令以及win+R即可运行的命令
  9. 在excel中数字比对_Excel数据比对,多种方法总有一个适合你
  10. OSN 3500 SDH智能光传输系统整机与单板技术分享
  11. win10热点手机显示IP配置错误连不上和电脑连上网线没网络
  12. Crazy Kids
  13. AHRS姿态解算说明(加速度+陀螺仪+磁力计原理及原始数据分析)
  14. 如何实现页面广告随时上下线、过期自动下线及到时自动上线
  15. 新一代智能视频云发展现状分析:五大要素成关键
  16. 用phpcms切换中英文网页的方法(不用解析二级域名)、phpcms完成pc和手机端切换(同一域名)...
  17. [FAQ12112]在电池低电压时,如何关闭camera的闪光功能
  18. 手机服务器艰辛之路(一)~手机服务器环境部署
  19. iOS 面试题集合~[有答案]
  20. CSDN《原力计划—打卡挑战》为你而来,新升级, 多流量,抓住春季的小尾巴,冲冲冲!

热门文章

  1. VC dll 注入之钩子注入
  2. 使用matlab对图像进行傅里叶变换
  3. C语言execvp实现简易Shell的两种方法
  4. 计算机管理恢复分区,如何在Windows中擦除恢复分区 | MOS86
  5. 网盘限速怎么办? 小编来支招!
  6. 任务计划服务程序在哪里
  7. android inactive InputConnection
  8. Promise.all中对于reject的处理
  9. 计算机动作路径教案,《引导路径动画》教案
  10. IP数据包在网络中的传输过程