38

Is there any way to detect if the client is using a touchpad vs. a mouse with Javascript?

Or at least to get some reasonable estimate of the number of users that use touchpads as opposed to mice?

Fragsworth
  • 33,919
  • 27
  • 84
  • 97

9 Answers9

29

Compare e.wheelDeltaY and e.deltaY (or e.deltaMode in Firefox) to detect touchpad mouse device

function handler(e) {
    var isTouchPad = e.wheelDeltaY ? e.wheelDeltaY === -3 * e.deltaY : e.deltaMode === 0
    // your code
    document.body.textContent = isTouchPad ? "isTouchPad" : "isMouse"
}
document.addEventListener("mousewheel", handler, false);
document.addEventListener("DOMMouseScroll", handler, false);
Lauri
  • 1,298
  • 11
  • 13
28

The answer above by Lauri seems to work, but it took me a while to understand why it works. So here I'll provide a slightly more human readable version, along with a conceptual explanation. First, that same code written out in a human readable manner:

function detectTrackPad(e) {
  var isTrackpad = false;
  if (e.wheelDeltaY) {
    if (e.wheelDeltaY === (e.deltaY * -3)) {
      isTrackpad = true;
    }
  }
  else if (e.deltaMode === 0) {
    isTrackpad = true;
  }
  console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}

document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);

This works because wheelDeltaY measures the physical distance that the actual hardware mouse wheel has travelled, while deltaY measures the amount of scrolling produced on screen. A conventional mouse typically has a much lower "scroll resolution" than a trackpad. That is to say, with a trackpad you can make a tiny motion and a get a tiny scroll on screen. A conventional mouse scrolls in chunkier, low resolution clicks. To complete a full rotation of the mouse wheel, it might make 10 clicks. There is no such thing as a half click or quarter click.

For a conventional mouse, a single wheel click is reported as 120 wheelDeltaY "units" and results in about ~100px worth of scrolling. The physical wheelDeltaY unit is a completely arbitrary number, it is not measuring inches or degrees or anything like that. The number 120 was selected simply because it has a lot of useful factors. The amount of scrolling on screen is represented by deltaY, and it varies significantly by browser. (Sidenote, deltaY is generally measured in "lines" not pixels, though it's complicated, see previous link).

mouse cutaway

Interacting with a trackpad is different in two ways. First of all, you can get wheelDeltaY values much smaller than 120, because very subtle finger gestures are detectable. Second, the wheelDeltaY is exactly 3x the deltaY value (at least in every browser I've managed to test). So, for instance, if you make a physical finger gesture equal to 12 click units, it will generally result in 4 pixels worth of scrolling. Lauri's code uses this second property (Y1 = Y2 * 3) to detect the existence of a trackpad, but you could probably also be successful simply by checking if abs(wheelDeltaY) equals 120

I haven't tested this, but I think it would also work:

function detectTrackPad(e) {
  var isTrackpad = false;
  if (e.wheelDeltaY) {
    if (Math.abs(e.wheelDeltaY) !== 120) {
      isTrackpad = true;
    }
  }
  else if (e.deltaMode === 0) {
    isTrackpad = true;
  }
  console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}

document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);
Matt Korostoff
  • 1,927
  • 2
  • 21
  • 26
  • Excellent solution, working as expected. Just a note - if you are using this in jquery's `.on('mousewhell wheel') ...` callback, then don't forget to pass original event, not that event's wrapper: `detectTrackPad(e.originalEvent)` – Starwave Sep 25 '20 at 12:39
  • Would be nice if you could get the direction and magnitude from this (Y direction) without needing the window to scroll (e.g. if y-overflow: hidden) – Cybernetic Jun 16 '21 at 02:38
  • Safari gives wheelDelta 12 instead of 120 for one wheel click, and performs some odd acceleration calculation for rapid scrolling. Other browsers also may display 240 etc. for multiple scrolls within an event. – Tronic Jul 16 '21 at 10:29
  • 1
    In Firefox, `deltaMode` is always 0, and `wheelDeltaY == -3 * deltaY` both for trackpad panning and wheel scrolling. `wheelDeltaY` is also a multiple of 16 rather than 120 in FF, which happens too frequently to be reliable, so I don't see a way to fix this in FF. In Chrome, `wheelDeltaY` may be larger multiples of 120, and has the `devicePixelRatio` multiplied in, and may still accidentally hit 120n from time to time. (This is tested on a Mac; it may be different on other systems). – trbabb Aug 11 '22 at 00:08
21

This topic may be already solved, but the answer was there is no way to detect it. Well I needed to get a solution, it was very important. So I found a acceptable solution for this problem:

var scrolling = false;
var oldTime = 0;
var newTime = 0;
var isTouchPad;
var eventCount = 0;
var eventCountStart;

var mouseHandle = function (evt) {
    var isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
    console.log(isTouchPadDefined);
    if (!isTouchPadDefined) {
        if (eventCount === 0) {
            eventCountStart = new Date().getTime();
        }

        eventCount++;

        if (new Date().getTime() - eventCountStart > 100) {
                if (eventCount > 10) {
                    isTouchPad = true;
                } else {
                    isTouchPad = false;
                }
            isTouchPadDefined = true;
        }
    }

    if (isTouchPadDefined) {
        // here you can do what you want
        // i just wanted the direction, for swiping, so i have to prevent
        // the multiple event calls to trigger multiple unwanted actions (trackpad)
        if (!evt) evt = event;
        var direction = (evt.detail<0 || evt.wheelDelta>0) ? 1 : -1;

        if (isTouchPad) {
            newTime = new Date().getTime();

            if (!scrolling && newTime-oldTime > 550 ) {
                scrolling = true;
                if (direction < 0) {
                    // swipe down
                } else {
                    // swipe up
                }
                setTimeout(function() {oldTime = new Date().getTime();scrolling = false}, 500);
            }
        } else {
            if (direction < 0) {
                // swipe down
            } else {
                // swipe up
            }
        }
    }
}

And registering the events:

document.addEventListener("mousewheel", mouseHandle, false);
document.addEventListener("DOMMouseScroll", mouseHandle, false);

It may need some optimization and is maybe less than perfect, but it works! At least it can detect a macbook trackpad. But due to the design i'd say it should work anywhere where the pad introduces a lot of event calls.

Here is how it works:

When the user first scrolls, it will detect and check that in 50ms not more than 5 events got triggered, which is pretty unusual for a normal mouse, but not for a trackpad.

Then there is the else part, which is not for importance for the detection, but rather a trick to call a function once like when a user swipes. Please come at me if I wasn't clear enough, it was very tricky to get this working, and is of course a less than ideal workaround.

Edit: I optimized the code now as much as I can. It detects the mouseroll on the second time and swipe on trackpad instantly. Removed also a lot of repeating and unnecessary code.

Edit 2 I changed the numbers for the time check and numbers of events called from 50 to 100 and 5 to 10 respectively. This should yield in a more accurate detection.

David Fariña
  • 1,536
  • 1
  • 18
  • 28
5

You could detect JS events.

A touch device will fire touch events such as touchstart in addition to mouse events.

A non-touch device will only fire the mouse events.

DA.
  • 39,848
  • 49
  • 150
  • 213
  • 5
    For anyone looking at this question recently, I've been searching around and I don't think this is true across all browsers, if any. Would love to see a source to show that trackpads trigger touch events. – minorcaseDev Jan 30 '17 at 19:52
  • 1
    @minorcase hmm...now that I re-read the question, I'm not sure if the OP was referring to handheld devices specifically, or any touch input peripheral. I agree, a touch pad (say, hooked up to a computer) wouldn't be *seen* as a touch device. My answer was specifically about the device as a whole (ie, a mobile browser vs desktop browser). – DA. Jan 30 '17 at 20:02
  • 1
    So, to be clear, yea, if we're talking about differentiating a mouse click from a touchpad click on something like a laptop, AFAIK, you can't differentiate those via JS. – DA. Jan 30 '17 at 20:04
  • Ah gotcha. I just assumed that they meant like a trackpad, but I just re-read it as well and the wording is kind of ambiguous. I just assumed they meant trackpad, but if they meant touch device then your answer is very correct! Thanks for clarifying. – minorcaseDev Jan 30 '17 at 22:08
5

Wheel event triggered by touchpad will give much smaller event.deltaY,1 or 2,but trigger by mouse wheel will give like 100,200.

Fei Sun
  • 361
  • 1
  • 3
  • 10
  • This is the best answer. Thank you! – abelabbesnabi Sep 05 '19 at 16:11
  • 1
    Actually this is true to some extent but not always like if you scroll from touch pad and it gives you last value 1 then on mouse scroll, on first scroll it will give you same value 1 then after that it will change. – Harry Aug 24 '22 at 19:34
4

In the general case, there is no way to do what you want. ActiveX might allow you to see and examine USB devices, but in the best case, even if that is somehow possible, that limits you to IE users. Beyond that, there is no way to know.

You might be able to discern patterns in how (or how often) a touchpad user moves the cursor versus how a mouse user might move the cursor. Differentiating between physical input devices in this way is an absurdly difficult prospect, and may be wholly impossible, so I include here it for completeness only.

apsillers
  • 112,806
  • 17
  • 235
  • 239
2

Trust me. This is the easiest and only solution that also works for Safari (as far as I know).

isTrackPad(e) {
  const { deltaY } = e;
  if (deltaY && !Number.isInteger(deltaY)) {
    return false;
  }
  return true;
}
kaz
  • 23
  • 3
0

From testing plugging in a mouse to a mac which does have a touchpad and also a windows machine with one, I can summarize how I got this working.

  1. Detect if the navigator user agent contains "Mobile" or "Mac OS" If either of these are true, it is likely a touch based system, but work to eliminate that. Set boolean hasTouchPad to true

  2. If the above is true, detect "mouse" events and run a test like high numbers, or a frequency of non integers or Kalman filtering.

  3. Keep these in a queue and if that queue's sum passes a threshold, disable the hasTouchpad variable and disconnect the event.

let isMouseCounts: Array<number> = []

if (Client.hasTouchpad) {
    document.addEventListener('wheel', detectMouseType);
}

function detectMouseType(e:WheelEvent) {
    if (!Client.hasTouchpad) return

    let isMouse = e.deltaX === 0 && !Number.isInteger(e.deltaY)

    isMouseCounts.push(isMouse ? 1 : 0)
    if (isMouseCounts.length > 5) isMouseCounts.shift()

    let sum = isMouseCounts.reduce(function(a, b) { return a + b; });

    if (sum > 3 && e.type === "wheel") {
        console.log("Touchpad disabled")
        document.removeEventListener('wheel', detectMouseType);
        Client.hasTouchpad = false;
    }
}
Joel Teply
  • 3,260
  • 1
  • 31
  • 21
  • This is typescript, right? Could you maybe provide a JS version? That's what the question asked for, and I have no way to test this solution without spinning up a whole webpack build. – Matt Korostoff Jun 16 '20 at 14:50
-2

You could just check for the device driver softwares installed into the local package as functioning. Like in windows synaptics, elan hardware, as for UNIX(Linux) you could just check for the package installed during the basic installed onto. A lot of packages come in different formats in different versions of Linux and Linux like systems(Not linux entirely) but they use the same package name for all. Just got to know the code to pull it. Still working on it.