如何用JavaScript偵測使用者在用觸控板還是滑鼠?
這可能不是很常見的問題,可是最近一個新的案子需要重新開發一個自己的 JavaScript 的全面的滑塊 (full-page slider)。這個滑塊的功能很接近知名的函式庫 fullPage.js. 可是跟很多自己寫的東西一樣,我們在很多不同的裝置碰到一些奇怪的 bug。比如用滑鼠操作時一切都很正常,但是使用觸控板時,滑塊會一次滑動太多頁。原因就是所謂的 momentum scroll。
什麼是 momentum scroll?
Momentum scroll 立本上是一個現代的筆電觸控板滑動的實作方式。為了提供更好地使用經驗,觸控板滑動的方式有點模仿手機的滑動的效果。根據你滑動的力量,滑動的速度與時間會不一樣。它一開始會比較快,然後會逐漸速度直到停止,不像滑鼠的滾輪一步一步。
舉例來說,如果你建立一個簡單的 scroll event,你可以在你的檢查工具看到,每次滑動的時候 window 位置的位置時多少。我「滑動」的意思就是一次滑鼠滾輪的動作,或是觸控板一次滑動。
document.addEventListener("scroll", () => {
const scrollY = window.scrollY
console.log(scrollY)
});
如果你用滑鼠滾輪滑動,你應該會看到一個value:
400
這個表示 window 往下移動了400px
但如果你用觸控板,你會看到不一樣的結果:
3
14
48
103
225
313
403
491
576
658
741
820
894
964
...
1780
1781
1782
1783
1784
你可以很清楚地看到,滑動一開始加速得很快,然後後面慢慢減速,這樣子能帶來更順的滑動經驗。另外,我們可以看到一個很重要的差異:
觸控板一次滑動會觸發很多scroll event,但是滑鼠的滾輪應該只會觸發一次而已。.
第一種方式:Timeout
前面提到的 fullPage.js 函式庫會在每次切換頁面加上 700ms 的 delay,這樣子就能避免 momentum scroll 造成多次不要的滑動。簡單來說,就是每次切換頁面時,會暫時鎖著 scrolling 700ms。如果你的 transition 的長度 700ms 以上,這個做法沒有什麼問題。但是我們滑塊的 transition 比較短,因此我們不想讓使用者等這麼久,不然反應會很卡。
而且還有一個問題,你應該無法保證每一台電腦的 momentum scroll 都會在 700ms 以內結束。所以我認為不要一直去調「剛剛好」的 timeout 數字,而直接去找別的解決方式。
偵測觸控板(trackpad)
我認為最好的解決方式是偵測使用者是否在用觸控板 (momentum scroll) ,然後在 momentum scroll 的效果結束之前,先鎖著來自同一個滑動動作的 scroll event。這個想法其實很簡單,我們只要去觀察滑動的速度在變化。我們會先建立一個 object,裡面包含兩個屬性:前一次 scroll 的 window 位置(以 pixel 為單位)和當下的時間戳:
window.momentum = {
previousScroll: 0,
timestamp: Date.now()
}
接下來,每次觸發 scroll 時,我們可以利用上一次的 scrollY 的位置來計算現在和上一次的距離變化,然後更新 deltaY
的 object。
document.addEventListener("scroll", () => {
const currentScroll = window.scrollY
const deltaY = currentScroll - window.momentum.previousScroll
const timestamp = Date.now()
// your extra logic
window.momentum = {
previousScroll: currentScroll,
timestamp: timestamp
}
});
這樣子,scrollY 的位置可以在不同的 scroll 的動作之間保留下來,讓我們判斷目前這次滑動動作是不是屬於同一個 momentum scroll 還是完全新的 scroll。如果是的話,就可以把它鎖著。在我的 project 裡面,我判斷方式是這樣子:如果上一次的 scroll event 發生在 50ms 內,而且 scrollY 的變化小於 200px,那就很有可能是同一次滑動的部分:
const deltaY = currentScroll - window.momentum.previousScroll
const deltaT = timestamp - window.momentum.timestamp
const isMomentum = deltaY < 200 && deltaT < 50
if (!isMomentum) {
nextSlide()
}
這兩個數字(200px 和 50ms)是我經過幾次嘗試才抓出來的,但是效果是OK的。這個方式解決了所有的 momentum scroll 的問題。同樣的邏輯可以用在手機上,因為手機的滑動也是有 momentum scroll 的效果。