How to detect trackpad in Javascript?
This is probably not a very common issue, but for a new project that I was working on, we had to create a custom full-page slider. The slider was similar to a well-known library fullPage.js. As with many things built from scratch, we started hitting edge cases on different platforms. The slider worked fine when using a mouse, but it would scroll too many slides at once when using a trackpad. The reason was momentum scroll.
What is momentum scroll?
Momentum scroll is basically how trackpads are implemented in most modern laptops. In order to make user experience smoother, trackpad scrolling resembles the scrolling in smartphones - depending on the “strength” of your scroll, the duration and speed of scrolling will vary, and then the scrolling will gradually slow towards the end (not suddenly stop like id does with a mouse).
For example, if you create a simple scroll event listener, you can see the scrolling position is during each “scroll”. By “scroll” I mean either one movement of the mouse wheel or one swipe of a trackpad.
document.addEventListener("scroll", () => {
const scrollY = window.scrollY
console.log(scrollY)
});
For mouse wheel you will see one value per scroll like:
400
This means the window moved 400px down.
With a trackpad, you’ll see something completely different:
3
14
48
103
225
313
403
491
576
658
741
820
894
964
...
1780
1781
1782
1783
1784
You can see how fast the scrolling distance speeds up in the begging and slows down towards the end, in order to create a smooth scrolling experience. This also shows another very important thing:
Trackpad will trigger many scroll events from a single “swipe”, unlike a mouse - which will trigger only one per wheel spin.
First solution: Timeout
The fullPage.js library that I mentioned earlier is setting a 700ms delay between slides to prevent unwanted scrolls caused my momentum scroll on laptops. This basically means that scrolling will be locked for 700ms. This would be okay if the transition was as least 700ms, but in our case this wasn’t possible because we wanted everything to feel snappy, thus the transition was short.
Another issue is that you can never be sure that momentum scroll will finish within 700ms on all laptops, so instead of coming up with the perfect delay value that covers all edge cases, we should think of a different more reliable solution.
Detecting trackpad
The best way to solve this would probably be detecting a trackpad (momentum scroll) being used and locking further scroll until the momentum scroll effect finishes. The idea is simple, we need to check how the scrolling speed is changing over time. We will start with declaring a new object with two properties: previous scroll value in pixels and a timestamp:
window.momentum = {
previousScroll: 0,
timestamp: Date.now()
}
So on every scroll you’ll have to override the deltaY
object with new values. Also, we will use the previous scrollY
value to calculate the change in pixels between current and the last scroll:
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
}
});
This way, scrollY
values can be shared between different scroll events - it lets us determine if the current event is a part of the ongoing momentum scroll effect, and if so - block it. For my project I assumed that if the last scroll event was issued less than 50ms ago and the scrollY changed by lees than 200px, it is definitely part of the same swipe:
const deltaY = currentScroll - window.momentum.previousScroll
const deltaT = timestamp - window.momentum.timestamp
const isMomentum = deltaY < 200 && deltaT < 50
if (!isMomentum) {
nextSlide()
}
It took me some trial and error to come up with those values, but 200/50 has worked great and this “trick” prevents all unwanted scrolls for my project. The same logic can be applied to mobile, as mobile devices use momentum scroll as well.