移動端模擬滾動

連城發表於2018-09-10

背景

由於移動端原生滾動的侷限性以及相容性,部分特定場景的需求無法滿足。例如,筆者最近就接了一個需求:整個頁面分為三塊,每塊內容的高度不等(但都超過一屏),要求滾動到內容的臨界點有一個停頓的效果,下拉可以看到下一塊的部分內容,滿足條件則滑到下一塊內容。這種場景下,原生的滾動根本無法支援。因此,本文的主角就亮相了:模擬滾動,即儘可能的模擬原生滾動,但是又提供了一些擴充套件,滿足複雜場景的需求。

本文將從模擬滾動需要實現的功能、技術分析和方案來進行闡述,通讀本文,讀者將對模擬滾動的常見功能和技術要點有一定了解。

示例:模擬滾動

知識點

通過移動端的touch系列事件觸發模擬滾動,獲取手指滑動的偏移量,進而改變translateY來進行位置偏移。

滾動容器

滾動容器擁有高度,滾動區域的高度大於滾動容器,在滾動時,我們對滾動區域進行偏移,以達到滾動的視覺效果。

通過滾動區域的高度可以通過offsetHeight獲取,但是在以下情況下會遠遠小於實際高度。

  • 內聯樣式:在DOM節點生成時,樣式還未渲染完成,此時獲得的高度是預設樣式的高度,待樣式渲染完成後,高度可能會有變化
  • 圖片高度:img節點的高度開始是0,在圖片載入完成時,才會等於圖片高度,因此這裡也會存在誤差

慣性滾動

我們知道,為了讓滾動更加流暢,原生的滾動會有一個慣性滾動的效果,即手指快速滑動鬆開後,滾動區域會繼續滾動一段距離後停止。

為了實現這個功能,我們需求知道手指滑動的速度,根據比率計算目標滾動位置,然後驅動滾動,讓其到達目標位置。

這裡筆者嘗試了兩種方案:

  1. 通過requestAnimationFrame不斷進行偏移,直到到達目標位置
  2. 使用transition進行過渡,設定動畫曲線讓其到達目標位置

筆者對比了兩種方案,最終選擇了方案2,原因是transition過渡會更加的流暢,而requestAnimationFrame會有略微的卡頓,但是transition過渡,我們實時觸發滾動事件時,不好拿到其當前的位置,查閱了一些資料,筆者最終找到了解決方法,即getComputedStyle,這個API可以拿到當前頁面渲染的實時樣式,也就是說,哪怕它處於過渡動畫中,我們可以實時拿到它的真實位置。

邊界回彈

當滾動超出邊界時,通常我們還可以讓其繼續滾動,但是這時候會設定阻礙,即滾動速度慢下來,當滾動停止時,我們再將其拽回到邊界線。我們可以通過監聽transitionend事件來判斷慣性滾動停止,這裡的技術點不做過多分析,感興趣可以在文末中的原始碼找答案。

預設行為

通常情況下,我們需要阻止瀏覽器的預設行為(如滾動),但是這樣也會誤殺一些我們需要的預設行為(如超連結跳轉、輸入框聚焦)。

解決方法很簡單,在touchstart觸發時,我們判斷一下目標節點是否需要阻止預設行為,比如說tagName=INPUT,我們不阻止預設行為。

點選事件

預設行為被阻止,繫結在子節點上的點選事件就無法觸發了,因此這裡我們需要判斷一下是否需要觸發點選事件。可以通過touch系列事件模擬點選行為,然後通過document.createEvent('Event')來主動觸發click事件。

滾動指示器

在滾動區域中,通常在右側會有一個指示器,用於檢視當前在整個內容區塊的大概位置。

為了方便使用,筆者註冊了一系列的鉤子,方便使用者呼叫,scroll鉤子就是其中之一,在滾動的時候它會實時觸發,在這裡就派上用場了。我們通過scroll鉤子改變指示器的位置,唯獨要注意的是滾動超出邊界時,指示器會變短然後恢復。

@axe/scroller

基於以上知識點和技術分析,筆者寫了一個模擬滾動js庫(無任何依賴):github.com/ansenhuang/…

相關文章