JavaScript markdown 編輯器實(shí)現(xiàn)雙屏同步滾動
前言
由于一直在使用 markdown 編輯器寫技術(shù)文章,所以對于編寫體驗(yàn)很敏感。我發(fā)現(xiàn)各大社區(qū)的 markdown 編輯器基本都有同步滾動功能。只不過有些做得好,有些做得馬馬虎虎。出于好奇,我就打算自己親自實(shí)現(xiàn)一下這個功能。
思考了一段時間,最后想出來了三種方案:
- 百分比滾動
- 雙屏同時渲染占用面積大的元素
- 每一行的元素都賦上一個索引,根據(jù)索引來精確同步每一行的滾動高度
百分比滾動
假設(shè)現(xiàn)在正在滾動 a 屏,那 a 屏的滾動百分比計(jì)算方式為:a 屏的滾動高度 / a 屏的內(nèi)容總高度
,用代碼表示 a.scrollTop / a.scrollHeight
。當(dāng)滾動 a 屏?xí)r,需要手動同步 b 屏的滾動高度,也就是根據(jù) a 屏的滾動百分比算出 b 屏的滾動高度:
a.onscroll = () => { b.scrollTo({ top: a.scrollTop / a.scrollHeight * b.scrollHeight }) }
原理就是這么簡單,可惜實(shí)現(xiàn)效果不太好。
從上面的動圖可以看出,當(dāng)我在第二個大標(biāo)題處停留的時候,左右雙屏的內(nèi)容是同步的。但當(dāng)我滾動到第三個大標(biāo)題時,左右雙屏的內(nèi)容高度已經(jīng)差了將近 300 像素了。所以說這個方案勉勉強(qiáng)強(qiáng)能用吧,聊勝于無。
雙屏同時渲染占用面積大的元素
雙屏內(nèi)容高度不一致,是因?yàn)?markdown 同一個元素渲染后的高度和渲染前會有差別。例如一個圖片,用 markdown 寫就一行代碼的事,但渲染出來的圖片有大有小,高度幾十、幾百像素的都有。如果 markdown 的圖片代碼雙屏同時渲染,倒是能解決這個問題。
但是除了圖片仍然有不少元素渲染前后的高度是有差距的,雖然沒有圖片這么夸張。譬如 h1 h2 這種,當(dāng)文章內(nèi)容越長,這種小差異帶來的問題會越來越大,導(dǎo)致雙屏內(nèi)容高度的差距也會越來越大。所以說這種方案也不是很靠譜。
賦上一個索引
每一行的元素都賦上一個索引,根據(jù)索引來精確精確同步每一行的滾動高度
之前兩個方案都屬于勉強(qiáng)能用,不夠好。現(xiàn)在這個第三方案就比前面兩個強(qiáng)多了,幾乎能做到精確同步每一行的內(nèi)容。具體怎么做呢?
第一步,監(jiān)聽 markdown 編輯框的內(nèi)容變化,為每一個元素賦上一個索引,空行空文本除外。
當(dāng)把編輯框的 HTML 傳給右邊的框渲染時,需要把 data-index
賦值給渲染后的元素。這樣就能通過 data-index
精確定位渲染前后的同一元素了。
第二步,根據(jù) a 屏的元素滾動高度計(jì)算 b 屏上同一索引的元素滾動高度
在 a 屏進(jìn)行滾動時,需要從上到下遍歷 a 屏的所有元素,并且找到第一個在屏幕內(nèi)的元素。找到第一個在屏幕內(nèi)的元素
這句話的意思是因?yàn)樵跐L動過程中,有些元素會因?yàn)闈L動跑到屏幕外面(原來在屏幕內(nèi),滾動到屏幕外),這些元素我們是不需要計(jì)算的。
判斷一個元素是否在屏幕內(nèi):
// dom 是否在屏幕內(nèi) function isInScreen(dom) { const { top, bottom } = dom.getBoundingClientRect() return bottom >= 0 && top < window.innerHeight }
除了判斷元素是否在屏幕內(nèi),還需要判斷這個元素在屏幕內(nèi)的部分占整個元素高度的百分比。譬如說一個圖片的 markdown 字符串,由于滾動的原因,導(dǎo)致一半在屏幕內(nèi),一半在屏幕外。為了精確同步,那么渲染后的圖片也必須有一半在屏幕內(nèi)一半在屏幕外。
計(jì)算元素在屏幕內(nèi)的百分比代碼:
// dom 在當(dāng)前屏幕展示內(nèi)容的百分比 function percentOfdomInScreen(dom) { // 已經(jīng)通過另一個函數(shù) isInScreen() 確定了這個 dom 在屏幕內(nèi),所以只需要計(jì)算它在屏幕內(nèi)的百分比,而不需要考慮它是否在屏幕外 const { height, bottom } = dom.getBoundingClientRect() if (bottom <= 0) return 0 // 不在屏幕內(nèi) if (bottom >= height) return 1 // 完全在屏幕內(nèi) return bottom / height // 部分在屏幕內(nèi) }
現(xiàn)在我們就可以從上到下遍歷 a 屏的所有元素,找到第一個在屏幕內(nèi)的元素了:
// scrollContainer 即上面說的 a 屏,ShowContainer 是 b 屏 const nodes = Array.from(scrollContainer.children) for (const node of nodes) { // 從上往下遍歷,找到第一個在屏幕內(nèi)的元素 if (isInScreen(node) && percentOfdomInScreen(node) >= 0) { const index = node.dataset.index // 根據(jù)滾動元素的索引,找到它在渲染框中對應(yīng)的元素 const dom = ShowContainer.querySelector(`[data-index="${index}"]`) // 獲取滾動元素在 a 屏中展示的內(nèi)容百分比 const percent = percentOfdomInScreen(node) // 計(jì)算這個對等元素在 b 屏中距離容器頂部的高度 const heightToTop = getHeightToTop(dom) // 根據(jù) percent 算出對等元素在 b 屏中需要隱藏的高度 const domNeedHideHeight = dom.offsetHeight * (1 - percent) // scrollTo({ top: heightToTop }) 會把對等元素滾動到在 b 屏中恰好完全展示整個元素的位置 // 然后再滾動它需要隱藏的高度 domNeedHideHeight,組合起來就是 scrollTo({ top: heightToTop + domNeedHideHeight }) ShowContainer.scrollTo({ top: heightToTop + domNeedHideHeight }) break } }
從動圖來看,目前已經(jīng)做到行內(nèi)容的精確同步了。
踩坑
有一些元素渲染后會變成嵌套元素,例如表格 table,渲染后的內(nèi)容層級為:
<table> <tbody> <tr> <td></td> </tr> </tbody> </table>
按照目前的渲染邏輯,假如我寫了個表格:
|1|b| ...
那么 |1|b|
上的 data-index
會對應(yīng)到 table
上。
那這就會有個 bug,當(dāng) |1|b|
滾動到 50% 的時候,整個 table
也會滾動到 50%。
這個現(xiàn)象如下圖所示:
這和我們相要的效果不一樣。a 屏連一行的內(nèi)容都沒滾完,b 屏整個內(nèi)容已經(jīng)滾動到一半了。
所以像這種嵌套的元素,在打 data-index
標(biāo)記時,要把它打到真正的內(nèi)容上。用表格 table 來做示例,就得把 data-index
的標(biāo)記打在 tr
上。
這樣一來,同步滾動就正常了。同理,其他的嵌套元素也一樣(譬如 ul ol)。
到此這篇關(guān)于JavaScript markdown 編輯器實(shí)現(xiàn)雙屏同步滾動的文章就介紹到這了,更多相關(guān)JS markdown 雙屏同步滾動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS庫particles.js創(chuàng)建超炫背景粒子插件(附源碼下載)
particles.js用于創(chuàng)建粒子的輕量級 JavaScript 庫。使用方法非常簡單,代碼也很容易實(shí)現(xiàn),下面通過本文給大家分享JS庫particles.js創(chuàng)建超炫背景粒子插件附源碼下載,需要的朋友參考下吧2017-09-09js中string和number類型互轉(zhuǎn)換技巧(分享)
下面小編就為大家?guī)硪黄猨s中string和number類型互轉(zhuǎn)換技巧(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11值得分享和收藏的xmlplus組件學(xué)習(xí)教程
值得分享和收藏的xmlplus組件學(xué)習(xí)教程,xmlplus是一個設(shè)計(jì)非常獨(dú)特 JavaScript 框架,用于快速開發(fā)前后端項(xiàng)目,感興趣的小伙伴們可以參考一下2017-05-05JS獲取鼠標(biāo)坐標(biāo)的實(shí)例方法
這篇文章介紹了JS獲取鼠標(biāo)坐標(biāo)的實(shí)例方法,有需要的朋友可以參考一下2013-07-07JS實(shí)現(xiàn)六邊形3D拖拽翻轉(zhuǎn)效果的方法
這篇文章給大家分享一個利用javascript實(shí)現(xiàn)3D六邊形拖拽翻轉(zhuǎn)的效果實(shí)例,實(shí)現(xiàn)后的效果很贊,對大家的學(xué)習(xí)Javascript具有一定的參考借鑒價值,有需要的朋友們一起去來看看吧。2016-09-09bootstrap實(shí)現(xiàn)嵌套模態(tài)框的實(shí)例代碼
這篇文章主要介紹了bootstrap實(shí)現(xiàn)嵌套模態(tài)框的實(shí)例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01小程序云開發(fā)獲取不到數(shù)據(jù)庫記錄的解決方法
這篇文章主要為大家詳細(xì)介紹了小程序云開發(fā)獲取不到數(shù)據(jù)庫記錄的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05