当前位置: 首页 > Web前端 > HTML

markdown编辑器实现双屏同步滚动

时间:2023-03-28 19:31:07 HTML

由于一直使用markdown编辑器写技术文章,对写作体验非常敏感。我发现各大社区的markdown编辑器基本都有同步滚动的功能。只是有的做得好,有的一般。出于好奇,我打算自己实现这个功能。想了想,最终想到了三种解决方案:百分比滚动、同时占据大面积的元素双屏渲染、为每一行的元素分配一个索引、精确同步滚动每一行的高度根据index百分比滚动假设现在正在滚动一个屏幕,屏幕滚动百分比的计算方法是:屏幕的滚动高度/屏幕的总内容高度,表示为a.scrollTop/a.scrollHeight通过代码。滚动屏幕a时,需要手动同步屏幕b的滚动高度,即根据屏幕a的滚动百分比计算屏幕b的滚动高度:a.onscroll=()=>{b.scrollTo({top:a.scrollTop/a.scrollHeight*b.scrollHeight})}原理就这么简单,可惜实现效果不是很好。从上面的动画可以看出,当我停留在第二个大标题时,左右双屏的内容是同步的。但是当我滚动到第三个大标题时,左右双屏的内容高度相差了将近300像素。所以这个方案勉强能用,聊胜于无。双屏渲染同时占据大面积的元素双屏内容的高度不一致是因为同一个markdown元素渲染后和渲染前的高度不一样。比如一张图片可以用markdown写一行代码,但是渲染出来的图片可大可小,高度可以是几十像素,也可以是几百像素。如果同时在两个屏幕上渲染markdown的图片代码,就可以解决这个问题。不过除了图片之外,还有很多渲染前后高度不同的元素,虽然没有图片那么夸张。比如h1h2,当文章内容比较长的时候,这个小差异带来的问题会越来越大,导致双屏内容的高度差距变大。所以这个解决方案不是很可靠。每行的元素都分配了一个索引,每行的滚动高度根据索引精确同步。前两个解决方案勉强可用,也不够好。现在这第三种方案比前两种强很多,几乎可以准确同步每一行的内容。怎么做?第一步是监听markdown编辑框的内容变化,给每个元素分配一个索引,空行和空文本除外。将编辑框的HTML传递给右框进行渲染时,需要给渲染的元素赋值data-index。这样就可以通过data-index精确定位渲染前后的同一个元素。第二步,根据a屏元素的滚动高度,计算b屏相同索引元素的滚动高度。在屏幕a上滚动时,需要从上到下遍历屏幕a的所有元素,找到屏幕中的第一个元素。找到屏幕第一个元素的意思就是在滚动的过程中,有些元素会因为滚动而跑出屏幕(本来在屏幕里面,滚动到屏幕外面),我们不需要计算这些元素。判断元素是否在屏幕中://dom是否在屏幕中functionisInScreen(dom){const{top,bottom}=dom.getBoundingClientRect()returnbottom>=0&&top=height)return1//完全在屏幕中returnbottom/height//部分在屏幕中}现在我们可以从上到下遍历一个屏幕的所有元素,找到屏幕中的第一个元素://scrollContainer就是上面说的那个屏幕,ShowContainerisbscreenconstnodes=Array.from(scrollContainer.children)for(constnodeofnodes){//从上到下遍历,找到屏幕中的第一个元素if(isInScreen(node)&&percentOfdomInScreen(node)>=0){constindex=node.dataset.index//一致到滚动元素的索引,在渲染框中找到其对应的元素constdom=ShowContainer.querySelector(`[data-index="${index}"]`)//获取滚动元素显示的内容百分比inscreenaconstpercent=percentOfdomInScreen(node)//计算这个等效元素距离容器顶部的高度inscreenbconstheightToTop=getHeightToTop(dom)//根据percent计算需要的等效元素的高度隐藏在b屏幕中constdomNeedHideHeight=dom.offsetHeight*(1-percent)//scrollTo({top:heightToTop})将对等元素滚动到整个元素在b屏幕中完全显示的位置//然后滚动到它的高度需要隐藏domNeedHideHeight,组合为scrollTo({top:heightToTop+domNeedHideHeight})S??howContainer.scrollTo({top:heightToTop+domNeedHideHeight})break}}从动画来看,至此已经实现了行内容的精确同步.元素渲染后,会成为一个嵌套元素,比如table表格。渲染的内容层级是:

根据目前的渲染逻辑,如果我写一个table:|1|b|...然后是|1|b|上的数据索引将对应表。那么就会出现bug,当|1|b|滚动到50%,整个表格也将滚动到50%。这种现象如下图所示:这和我们想要的效果不一样。a屏的一行内容还没有滚动完,b屏的全部内容已经滚动到一半了。所以像这样的嵌套元素,在标记data-index的时候,需要打在真正的内容上。以form表为例,必须在tr上标记data-index。这样同步滚动就正常了。其他嵌套元素(例如ulol)也是如此。总结一下完整的代码,我已经放在了github上:markdown-editor-sync-scroll-demo和在线DEMO:demo1demo2demo3demo4demo5demo6如果在线DEMO比较慢,可以clone项目后直接打开html文件访问。