Re: [w3c/uievents] Wheel event: determine difference between trackpad and mouse events (Issue #337)

Here ya go! Please note that it's not 100% reliable and for Windows users we just completely disable the check and always assume a scrollwheel (so no two-finger panning for Windows users on trackpads).

I hope this:
- Helps you in your case
- Illustrated to the W3C why we desperately need a native solution to this problem

```ts
/*
  For Mac, we want to tell the difference between a touchpad two-finger scroll and a mousewheel.
  Unfortunately there is no simple way to do this, because both devices trigger a 'wheel' event

  We can have a good guess with the following assumptions (in priority order):

  - Mac touchpads never produce deltas with a fractional part
  - Wheel deltas never happen on the X axis
  - Wheels often produce the same deltas twice in a row, or some multiple of a delta (e.g. 150, 300, 450)

  For windows users the above rules do not apply, we treat the wheel event the same, touchpad or mouse.

  Here's a sandbox for testing this function. Update the function and fork it, share it for user testing:
  https://codesandbox.io/s/inspiring-hill-b7evw
*/

import round from "lodash/round"

import { isMac } from "./detectPlatform"

type ScrollType = "wheel" | "touchPad" | "pinchZoom" | "undetermined"

const allowedDivisors = [0.25, 0.5, 0.333, 0.667, 1.5, 2, 3, 4]
const trackPadSlowMax = 20
const timeoutMs = 500

let timer: NodeJS.Timeout | undefined

// Checks sample of delta values
export const checkDeltasAreFromWheel = (arr: number[]) => {
  let prevVal: number | undefined
  let isWheel = false
  let wasOverMax = false
  let sameCount = 0
  let divisorCount = 0

  for (let i = 0; i < arr.length; i++) {
    const v = Math.abs(arr[i])

    if (v % 1 !== 0) {
      // Has irregular fractional part
      return true
    }

    if (prevVal && prevVal !== v) {
      // Values are different
      isWheel = false

      if (v > trackPadSlowMax && prevVal > trackPadSlowMax) {
        const test = round(v / prevVal, 3)

        if (allowedDivisors.includes(test)) {
          // Values are different but neatly divisible
          divisorCount++
        }
      }
    } else if (v > trackPadSlowMax) {
      // Values are same (and not low)
      sameCount++
    }

    if (v > trackPadSlowMax) {
      wasOverMax = true
    }

    prevVal = v
  }

  if (!wasOverMax) {
    // If the entire sample was under max, we can assume trackpad
    return false
  }

  if (divisorCount > arr.length / 2) {
    // If more than 50% of consecutive vals had neat divisors, we can assume wheel
    return true
  }

  if (sameCount > arr.length / 2) {
    // If more than 50% of consecutive vals were the same, we can assume wheel
    return true
  }

  return isWheel
}

let deltas: number[] = []

export const determineScrollType = (e: WheelEvent): ScrollType => {
  if (e.ctrlKey) {
    // ctrlKey is set to true when use pinchzoom on touchpad
    return "pinchZoom"
  } else if (!isMac()) {
    // Non-Mac users always get wheel type
    return "wheel"
  } else if (Math.abs(e.deltaX) > 0) {
    // If the delta is on the X axis, it must be a touchpad
    return "touchPad"
  }

  deltas.unshift(e.deltaY)
  if (deltas.length > 10) {
    deltas.pop()
  }

  // Clear deltas after some short period in case user has switched between mouse/touchpad
  if (timer) clearTimeout(timer)

  timer = setTimeout(() => {
    deltas = []
  }, timeoutMs)

  if (deltas.length > 2) {
    return checkDeltasAreFromWheel(deltas) ? "wheel" : "touchPad"
  }

  return "undetermined"
}
```

-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3c/uievents/issues/337#issuecomment-1354417620
You are receiving this because you are subscribed to this thread.

Message ID: <w3c/uievents/issues/337/1354417620@github.com>

Received on Friday, 16 December 2022 09:04:25 UTC