溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

發(fā)布時(shí)間:2021-06-29 11:01:18 來源:億速云 閱讀:167 作者:小新 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

在一些支持用 markdown 寫文章的網(wǎng)站,后臺(tái)寫作頁面,一般都是支持 markdown 即時(shí)預(yù)覽的,也就是將整個(gè)頁面分成兩部分,左半部分是你輸入的 markdown 文字,右半部分則即時(shí)輸出對應(yīng)的預(yù)覽頁面,例如下面就是 CSDN 后臺(tái)寫作頁面的 markdown 即時(shí)預(yù)覽效果:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

本文不是闡述如何從 0 實(shí)現(xiàn)這種效果的(后續(xù) 很可能 會(huì)單出文章,),拋開其他,單看頁面主體中左右兩個(gè)容器元素,即 markdown 輸入框元素和預(yù)覽顯示框元素

本文要探討的是,當(dāng)這兩個(gè)容器元素的內(nèi)容都超出了容器高度,即都出現(xiàn)了滾動(dòng)框的時(shí)候,如何在其中一個(gè)容器元素滾動(dòng)時(shí),讓另外一個(gè)元素也隨之滾動(dòng)。

DOM 結(jié)構(gòu)

既然是與滾動(dòng)條有關(guān),那么首先想到 js 中控制滾動(dòng)條高度的一個(gè)屬性: scrollTop ,只要能控制這個(gè)屬性的值,自然也就能控制滾動(dòng)條的滾動(dòng)了。

對于以下 DOM 結(jié)構(gòu):

<div id="container">
 <div class="left"></div>
 <div class="right"></div>
</div>

其中, .left 元素是左半部分輸入框容器元素, .right 元素是右半部分顯示框容器元素, .container 是它們共同的父元素。

由于需要溢出滾動(dòng),所以還需要設(shè)置一下對應(yīng)的樣式(只是關(guān)鍵樣式,非全部):

#container {
 display: flex;
 border: 1px solid #bbb;
}
.left, .right {
 flex: 1;
 height: 100%;
 word-wrap: break-word;
 overflow-y: scroll;
}

再向 .left.right 元素中塞入足夠的內(nèi)容,讓二者出現(xiàn)滾動(dòng)條,就是下面這種效果:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

樣式是出來個(gè)大概了,下面就可以在這些 DOM 上進(jìn)行一系列的操作了。

初次嘗試

大致思路,監(jiān)聽兩個(gè)容器元素的滾動(dòng)事件,在其中一個(gè)元素滾動(dòng)的時(shí)候,獲取這個(gè)元素的 scrollTop 屬性的值,同時(shí)將此值設(shè)置為另外一個(gè)滾動(dòng)元素的 scrollTop 值即可。

例如:

var l=document.querySelector('.left')
var r=document.querySelector('.right')
l.addEventListener('scroll',function(){
 r.scrollTop = l.scrollTop
})

效果如下:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

似乎很不錯(cuò),但是現(xiàn)在是不僅想讓右邊跟隨左邊滾動(dòng),還想左邊跟隨右邊滾動(dòng),于是再加以下代碼:

addEventListener('scroll',function(){
 r.scrollTop = l.scrollTop
})

看上去很不錯(cuò),然而,哪有那么簡單的事情。

這個(gè)時(shí)候你再用鼠標(biāo)滾輪進(jìn)行滾動(dòng)的時(shí)候,卻發(fā)現(xiàn)滾動(dòng)得有點(diǎn)吃力,兩個(gè)容器元素的滾動(dòng)似乎被什么阻礙住了,很難滾動(dòng)。

仔細(xì)分析,原因很簡單,當(dāng)你在左邊滾動(dòng)的時(shí)候,觸發(fā)了左邊的滾動(dòng)事件,于是右邊跟隨滾動(dòng),但是與此同時(shí)右邊的跟隨滾動(dòng)也是滾動(dòng),于是也觸發(fā)了右邊的滾動(dòng),于是左邊也要跟隨右邊滾動(dòng)...然后就進(jìn)入了一個(gè)類似于相互觸發(fā)的情況,所以就會(huì)發(fā)現(xiàn)滾動(dòng)得很吃力。

解決 scroll 事件同時(shí)觸發(fā)的問題

想要解決上述問題,暫時(shí)有以下兩種方案。

將 scroll 事件換成 mousewheel 事件

由于 scroll 事件不僅會(huì)被鼠標(biāo)主動(dòng)滾動(dòng)觸發(fā),同時(shí)改變?nèi)萜髟氐?scrollTop 也會(huì)觸發(fā),元素的主動(dòng)滾動(dòng)其實(shí)就是鼠標(biāo)滾輪觸發(fā)的,所以可以將 scroll 事件換成一個(gè)對鼠標(biāo)滾動(dòng)敏感而不是元素滾動(dòng)敏感的事件:'mousewheel',于是上述監(jiān)聽代碼變成了:

addEventListener('mousewheel',function(){
  r.scrollTop = l.scrollTop
})
r.addEventListener('mousewheel',function(){
  l.scrollTop = r.scrollTop
})

效果如下:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

似乎是有點(diǎn)用,但是實(shí)際上還有兩個(gè)問題。

當(dāng)滾動(dòng)其中一個(gè)容器元素的時(shí)候,另外一個(gè)容器元素雖然也跟著滾動(dòng),但滾動(dòng)得并不流暢,高度有明顯的瞬間彈跳

在網(wǎng)上找了一圈,沒有找到關(guān)于 wheel 事件滾動(dòng)頻率相關(guān)內(nèi)容,我推測這可能就是此事件的一個(gè) feature

鼠標(biāo)每次滾動(dòng)基本上都并不是以 1px 為單位的,其最小單元遠(yuǎn)比 scroll 事件小的多,我用我的鼠標(biāo)在 chrome 瀏覽器上滾動(dòng),每次滾過的距離都恰好是 100px ,不同的鼠標(biāo)或者瀏覽器這個(gè)數(shù)值應(yīng)該都是不一樣的,而 wheel 事件其實(shí)真正監(jiān)聽的是鼠標(biāo)滾輪滾過一個(gè)齒輪卡點(diǎn)的事件,這也就能解釋為何會(huì)出現(xiàn)彈跳的現(xiàn)象了。

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

一般來說,鼠標(biāo)滾輪每滾過一個(gè)齒輪卡點(diǎn),就能監(jiān)聽到一個(gè) wheel 事件,從開始到結(jié)束,被鼠標(biāo)主動(dòng)滾動(dòng)的元素已經(jīng)滾動(dòng)了 100px ,所以另外一個(gè)跟隨滾動(dòng)的容器元素也就瞬間跳動(dòng)了 100px

而之所以上述 scroll 事件不會(huì)讓跟隨滾動(dòng)元素出現(xiàn)瞬間彈跳,則是因?yàn)楦S滾動(dòng)元素每次 scrollTop 發(fā)生變化時(shí),其值不會(huì)有 100px 那么大的跨度,可能也沒有小到 1px ,但由于其觸發(fā)頻率高,滾動(dòng)跨度小,最起碼在視覺上就是平滑滾動(dòng)的了。

wheel 只是監(jiān)聽鼠標(biāo)滾輪事件,但如果是用鼠標(biāo)拖動(dòng)滾動(dòng)條,就不會(huì)觸發(fā)此事件,另外的容器元素也就不會(huì)跟隨滾動(dòng)了

這個(gè)其實(shí)很好解決,用鼠標(biāo)拖動(dòng)滾動(dòng)條肯定是能觸發(fā) scroll 事件的,而在這種情況下,你肯定能夠很輕易地判斷出這個(gè)被拖動(dòng)的滾動(dòng)條是屬于哪個(gè)容器元素的,只需要處理這個(gè)容器的滾動(dòng)事件,另外一個(gè)跟隨滾動(dòng)容器的滾動(dòng)事件不做處理即可。

wheel 事件的兼容問題

wheel 事件是 DOM Level3 的標(biāo)準(zhǔn)事件,但是除了此事件之外,還有很多非標(biāo)準(zhǔn)事件,不同的瀏覽器內(nèi)核使用不同的標(biāo)準(zhǔn),所以可能還需要按情況來進(jìn)行兼容,具體可見MDN MouseWheelEvent

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果 實(shí)時(shí)判斷

如果你難以忍受 wheel 的彈跳,以及各種兼容,那么其實(shí)還有另外的路可以走得通,依舊是 scroll 事件,只不過需要做一些額外的工作。

scroll 事件的問題在于,沒有判斷當(dāng)前主動(dòng)滾動(dòng)的是哪一個(gè)容器元素,只要確定了主動(dòng)滾動(dòng)的容器元素,這事就好辦了,例如上述使用 wheel 事件中,用鼠標(biāo)拖動(dòng)滾動(dòng)條之所以能夠使用 scroll 事件,就是因?yàn)槟軌蚝苋菀椎卮_定當(dāng)前主動(dòng)滾動(dòng)容器元素是哪一個(gè)。

所以,問題的關(guān)鍵在于,如何判斷出當(dāng)前主動(dòng)滾動(dòng)的容器元素,只要解決了這個(gè)問題,剩下的就很好辦了。

不論是鼠標(biāo)滾輪滾動(dòng)還是鼠標(biāo)按在滾動(dòng)條上拖動(dòng)滾動(dòng)條滾動(dòng),都會(huì)觸發(fā) scroll 事件,并且這個(gè)時(shí)候,在坐標(biāo)系 Z 軸上,鼠標(biāo)的坐標(biāo)肯定是位于滾動(dòng)容器元素所占的面積之內(nèi)的,也就是說,在 Z 軸上,鼠標(biāo)肯定是懸浮或者位于滾動(dòng)容器元素之上。

鼠標(biāo)在屏幕上移動(dòng)的時(shí)候,是可以獲取到鼠標(biāo)當(dāng)前坐標(biāo)的。

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

其中, clientXclientY 就是當(dāng)前鼠標(biāo)相對于視口的坐標(biāo),可以認(rèn)為,只要這個(gè)坐標(biāo)在某個(gè)滾動(dòng)容器的范圍內(nèi),則認(rèn)為這個(gè)容器元素就是主動(dòng)滾動(dòng)容器元素,容器元素的坐標(biāo)范圍可以使用 getBoundingClientRect 進(jìn)行獲取。

下面是鼠標(biāo)移動(dòng)到 .left 元素中的示例代碼:

if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // 進(jìn)入 .left元素中
}

這樣確實(shí)是可以的,不過考慮到兩個(gè)滾動(dòng)容器元素幾乎占據(jù)了整個(gè)屏幕面積,所以 mousemove 所要監(jiān)聽的面積未免有點(diǎn)大,對于性能可能要求較高,所以其實(shí)可以換成 mouseover 事件,只需要監(jiān)聽鼠標(biāo)有沒有進(jìn)入到某個(gè)滾動(dòng)容器元素即可,也省去上述的坐標(biāo)判斷了。

addEventListener('mouseover',function(){
 // 進(jìn)入 .left滾動(dòng)容器元素內(nèi)
})

當(dāng)確定了鼠標(biāo)主動(dòng)滾動(dòng)的容器元素是哪一個(gè)時(shí),只需要處理這個(gè)容器的滾動(dòng)事件,另外一個(gè)跟隨滾動(dòng)容器的滾動(dòng)事件不做處理即可。

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

嗯,效果很不錯(cuò),性能也很好, perfect ,可以收工嘍~

按比例滾動(dòng)

上述示例全部是在兩個(gè)滾動(dòng)容器元素的內(nèi)容高度完全一致的情況下的效果,如果這兩個(gè)滾動(dòng)容器元素的內(nèi)容高度不同呢?

那就是下面這種效果:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

可見,由于兩個(gè)滾動(dòng)容器元素的內(nèi)容高度不同,所以最大的 scrollTop 也就不同,就會(huì)出現(xiàn)當(dāng)其中一個(gè) scrollTop 值較小的元素滾到底時(shí),另外一個(gè)元素還停留在一半,或者當(dāng)其中一個(gè) scrollTop 值較大的元素才滾到一半時(shí),另外一個(gè)元素就已經(jīng)滾到底了。

這種情況很常見,例如你用 markdown 寫作時(shí),一個(gè)一級標(biāo)題標(biāo)記 # 在編輯模式下占用的高度,一般都是小于預(yù)覽模式占用的高度的,這樣就出現(xiàn)了左右兩側(cè)滾動(dòng)高度不一致的情況。

所以,如果將這種情況也考慮進(jìn)來的話,那么就不能簡單地為兩個(gè)滾動(dòng)容器元素相互設(shè)置 scrollTop 值那么簡單。

雖然無法固定住滾動(dòng)容器內(nèi)容的高度,但是有一點(diǎn)可以確定,滾動(dòng)條最大滾動(dòng)高度,或者說 scrollTop 的值,肯定是與滾動(dòng)容器內(nèi)容的高度與滾動(dòng)容器本身的高度呈一定的關(guān)系。

由于需要知道滾動(dòng)容器內(nèi)容的高度,還要存在滾動(dòng)條,所以需要給此容器元素加個(gè)子元素,子元素高度不限,就是滾動(dòng)容器內(nèi)容的高度,容器高度固定,溢出滾動(dòng)即可。

<div id="container">
 <div class="left">
	 <div class="child"></div>
 </div>
 <div class="right">
	 <div class="child"></div>
 </div>
</div>

結(jié)構(gòu)示例如下:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

通過我的觀察推論與實(shí)踐驗(yàn)證,已經(jīng)確定下來了它們之間的關(guān)系,很簡單,就是最基本的加減法運(yùn)算:

滾動(dòng)條的最大滾動(dòng)高度(scrollTopMax) = 滾動(dòng)容器內(nèi)容的高度(即子元素高度ch) - 滾動(dòng)容器本身的高度(即容器元素高度ph)

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

也就是說,如果已經(jīng)確定了滾動(dòng)容器內(nèi)容的高度(即子元素高度ch)與滾動(dòng)容器本身的高度(即容器元素高度ph),那么就一定能確定滾動(dòng)條的最大滾動(dòng)高度( scrollTop ),而這兩個(gè)高度值基本上都是可以獲取到的,所以就能得到 scrollTop

因此,想要讓兩個(gè)滾動(dòng)元素容器等比例上下滾動(dòng),即其中一個(gè)元素滾到頭或者滾到底,另外一個(gè)元素也能對應(yīng)滾到頭和滾到底,那么只要得到這兩個(gè)滾動(dòng)容器元素之間的 scrollTop 最大值的比例( scale )就行了。

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

確定了 scale 之后,實(shí)時(shí)滾動(dòng)時(shí),只需要獲取主動(dòng)滾動(dòng)容器元素的 scrollTop1 ,就能得到另外一個(gè)跟隨滾動(dòng)的容器元素對應(yīng)的 scrollTop2

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

思路弄清晰了,寫代碼就是很容易的事情了,效果如下:

如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果

關(guān)于“如何使用原生JS控制多個(gè)滾動(dòng)條同步跟隨滾動(dòng)效果”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯(cuò),請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

js
AI