關於在 iOS WebView 上取得即時捲動位置的實驗

TL;DR: 在慣性捲動的時候 JS 似乎會被暫停執行,所以沒辦法在中間做點什麼

今天在 Twitter 上看到 @medicalwei 的一篇推文,在問怎麼在 iOS 上處理捲動事件。點進去看,原來是他有一個專案是透過偵測捲動的位置、即時更新某一條 indicator。因為好像很有趣,我就也跳下去實驗看看。

首先是找一點跟 Touchevent 相關的資料。

iOS WebView 捲動網頁的事件流程

根據 Safari Web Content GuideOne-Finger Events 段落所描述,在 WebView 中用一隻手指做捲動的時候,整個流程應該如下圖:

  1. 手指按到螢幕,準備開始捲動 (touchstart)
  2. 手指在螢幕上移動中,會觸發一到多個 touchmove 事件
  3. 手指在螢幕上停止,觸碰結束 (touchend)
  4. 因為慣性捲動 (Inertial Scrolling),網頁繼續減速度捲動。但此時不觸發任何事件
  5. 網頁停止捲動,此時終於觸發 scroll 事件

所以在整個捲動的過程中,只有從 touchendscroll 中間是事件的真空狀態 — 也就是從手指在螢幕上的捲動手勢結束之後、到網頁真的停止捲動之前。其他時候都可以透過聽 event 的方式來取得目前網頁的捲動位置。

實驗方法

不過雖然這中間不會觸發事件,應該也不代表就沒辦法做?所以直覺上來說就想到兩個方法:

  1. 計算手勢停止瞬間的捲動速度、然後用 JavaScript 模擬慣性捲動的速度遞減,推估每一小段時間應該要捲動到的位置、然後以小單位時間定期「回報」這個估計出來的位置

  2. 直接跑 window.setTimeout,在慣性捲動的這段空白中定期觸發一個 function,來即時取得目前網頁的捲動位置。

實驗結果:失敗

但很不幸地,實驗結果是失敗的。經過多次實驗,我發現當 iOS WebView 正在做慣性捲動的時候,setTimeout 就會被暫停觸發。所以上面兩個基於 setTimeout 的方法基本上就 — 做不到。

以下面的 script 來說

實際上也還是只拿得到兩次:一次是在 touchend 觸發的時候、一次是在慣性捲動結束後、scroll 事件還沒有把 flag 關掉之前。事實上,如果在網頁一載入就開 logScroll() 且不設定停止條件,在捲動手勢後的慣性捲動中(也就是在 touchendscroll 之間)的 console log 也會斷掉。

結論

目前實在的結論是沒有找到可行的方法。我推測這個設計也許是因為在慣性捲動的時候,WebView 實際上是把整個網頁畫面當成材質丟給 GPU 去運算捲動的動畫。所以在這中間即便 JS 改動網頁什麼、也要等慣性捲動停止後才會做 repaint;或是為了某種資源上的考量,就把慣性捲動期間的 JS block 住了。

不知道還有沒有其他可能的解法?

comments powered by Disqus