使用 Javascript 检测摇动事件,适用于所有主要浏览器/设备(iOS、Android)

Bas*_*asj 3 javascript mobile accelerometer shake

我读过Javascript。监听 iPhone 震动事件?检测html5 mobile 中的晃动,这为检测手机“晃动”事件提供了一个很好的解决方案:

<script src="shake.js"></script>
<script>
var myShakeEvent = new Shake({threshold: 15, timeout: 1000});
myShakeEvent.start(); 
window.addEventListener('shake', function() { alert('shake!'); }, false); 
</script>
Run Code Online (Sandbox Code Playgroud)

不幸的是,这似乎不适用于最新的 iOS 设备,并且此问题表明应为最新的 iOS 版本授予特殊权限。请注意,此处的代码不容易在 shake.js 库中使用。

问题:截至 2022 年,有哪种方法可以使用 Javascript 检测“摇动”事件,适用于主要浏览器(Firefox、Chrome、Safari)和移动设备(iOS、Android)?

如果首先出现一个请求许可的弹出窗口(例如请求地理定位请求许可的弹出窗口),那就可以了。

jse*_*ksn 6

没有shake事件:存在的最接近的事件是devicemotion

\n

根据您问题的内容,我推断您只想订阅当设备加速超过某个阈值时触发的事件,并在可能的触发器之间有一个去抖延迟(超时)。

\n

使用您链接到的“shake.js”库作为参考,我编写了一个 TypeScript 模块,您可以使用它来完成基本相同的事情。它包括在启动时获得用户权限批准,但请记住,您必须调用该ShakeInstance.start()方法来响应用户启动的事件(例如单击按钮)。

\n
\n

注意:您根据 MDN 相关文档页面上的兼容性数据列出的环境支持模块中使用的方法。(值得注意的是,桌面 Safari 根本不支持 DeviceMotionEvent。)但是,我无权访问您列出的所有环境组合以便自己执行测试,因此我将把它留给您。

\n
\n

TS游乐场

\n
function createEvent <Type extends string, Detail>(\n  type: Type,\n  detail: Detail,\n): CustomEvent<Detail> & {type: Type} {\n  return new CustomEvent(type, {detail}) as CustomEvent<Detail> & {type: Type};\n}\n\nfunction getMaxAcceleration (event: DeviceMotionEvent): number {\n  let max = 0;\n  if (event.acceleration) {\n    for (const key of [\'x\', \'y\', \'z\'] as const) {\n      const value = Math.abs(event.acceleration[key] ?? 0);\n      if (value > max) max = value;\n    }\n  }\n  return max;\n}\n\nexport type ShakeEventData = DeviceMotionEvent;\nexport type ShakeEvent = CustomEvent<ShakeEventData> & {type: \'shake\'};\nexport type ShakeEventListener = (event: ShakeEvent) => void;\n\nexport type ShakeOptions = {\n  /**\n   * Minimum acceleration needed to dispatch an event:\n   * meters per second squared (m/s\xc2\xb2).\n   *\n   * https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/acceleration\n   */\n  threshold: number;\n  /**\n   * After a shake event is dispatched, subsequent events will not be dispatched\n   * until after a duration greater than or equal to this value (milliseconds).\n   */\n  timeout: number;\n};\n\nexport class Shake extends EventTarget {\n  #approved?: boolean;\n  #threshold: ShakeOptions[\'threshold\'];\n  #timeout: ShakeOptions[\'timeout\'];\n  #timeStamp: number;\n\n  constructor (options?: Partial<ShakeOptions>) {\n    super();\n    const {\n      threshold = 15,\n      timeout = 1000,\n    } = options ?? {};\n    this.#threshold = threshold;\n    this.#timeout = timeout;\n    this.#timeStamp = timeout * -1;\n  }\n  \n  // @ts-ignore\n  addEventListener (\n    type: \'shake\',\n    listener: ShakeEventListener | null,\n    options?: boolean | AddEventListenerOptions\n  ): void {\n    type Arg1 = Parameters<EventTarget[\'addEventListener\']>[1];\n    super.addEventListener(type, listener as Arg1, options);\n  }\n\n  dispatchEvent (event: ShakeEvent): boolean {\n    return super.dispatchEvent(event);\n  }\n\n  // @ts-ignore\n  removeEventListener (\n    type: \'shake\',\n    callback: ShakeEventListener | null,\n    options?: EventListenerOptions | boolean\n  ): void {\n    type Arg1 = Parameters<EventTarget[\'removeEventListener\']>[1];\n    super.removeEventListener(type, callback as Arg1, options);\n  }\n\n  async approve (): Promise<boolean> {\n    if (typeof this.#approved === \'undefined\') {\n      if (!(\'DeviceMotionEvent\' in window)) return this.#approved = false;\n      try {\n        type PermissionRequestFn = () => Promise<PermissionState>;\n        type DME = typeof DeviceMotionEvent & { requestPermission: PermissionRequestFn };\n        if (typeof (DeviceMotionEvent as DME).requestPermission === \'function\') {\n          const permissionState = await (DeviceMotionEvent as DME).requestPermission();\n          this.#approved = permissionState === \'granted\';\n        }\n        else this.#approved = true;\n      }\n      catch {\n        this.#approved = false;\n      }\n    }\n    return this.#approved;\n  }\n\n  #handleDeviceMotion = (event: DeviceMotionEvent): void => {\n    const diff = event.timeStamp - this.#timeStamp;\n    if (diff < this.#timeout) return;\n    const accel = getMaxAcceleration(event);\n    if (accel < this.#threshold) return;\n    this.#timeStamp = event.timeStamp;\n    this.dispatchEvent(createEvent(\'shake\', event));\n  };\n\n  async start (): Promise<boolean> {\n    const approved = await this.approve();\n    if (!approved) return false;\n    window.addEventListener(\'devicemotion\', this.#handleDeviceMotion);\n    return true;\n  }\n\n  stop (): void {\n    window.removeEventListener(\'devicemotion\', this.#handleDeviceMotion);\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

像这样使用:

\n
const shake = new Shake({threshold: 15, timeout: 1000});\n\nshake.addEventListener(\'shake\', ev => {\n  console.log(\'Shake!\', ev.detail.timeStamp, ev.detail.acceleration);\n});\n\n// Then, in response to a user-initiated event:\nconst approved = await shake.start();\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我不确定 SO 片段环境是否会导致演示出现问题,但我已经包含了 TS Playground 链接中已编译的 JS,以防万一:

\n
\n

\r\n
\r\n
"use strict";\nfunction createEvent(type, detail) {\n    return new CustomEvent(type, { detail });\n}\nfunction getMaxAcceleration(event) {\n    let max = 0;\n    if (event.acceleration) {\n        for (const key of [\'x\', \'y\', \'z\']) {\n            const value = Math.abs(event.acceleration[key] ?? 0);\n            if (value > max)\n                max = value;\n        }\n    }\n    return max;\n}\nclass Shake extends EventTarget {\n    constructor(options) {\n        super();\n        this.#handleDeviceMotion = (event) => {\n            const diff = event.timeStamp - this.#timeStamp;\n            if (diff < this.#timeout)\n                return;\n            const accel = getMaxAcceleration(event);\n            if (accel < this.#threshold)\n                return;\n            this.#timeStamp = event.timeStamp;\n            this.dispatchEvent(createEvent(\'shake\', event));\n        };\n        const { threshold = 15, timeout = 1000, } = options ?? {};\n        this.#threshold = threshold;\n        this.#timeout = timeout;\n        this.#timeStamp = timeout * -1;\n    }\n    #approved;\n    #threshold;\n    #timeout;\n    #timeStamp;\n    // @ts-ignore\n    addEventListener(type, listener, options) {\n        super.addEventListener(type, listener, options);\n    }\n    dispatchEvent(event) {\n        return super.dispatchEvent(event);\n    }\n    // @ts-ignore\n    removeEventListener(type, callback, options) {\n        super.removeEventListener(type, callback, options);\n    }\n    async approve() {\n        if (typeof this.#approved === \'undefined\') {\n            if (!(\'DeviceMotionEvent\' in window))\n                return this.#approved = false;\n            try {\n                if (typeof DeviceMotionEvent.requestPermission === \'function\') {\n                    const permissionState = await DeviceMotionEvent.requestPermission();\n                    this.#approved = permissionState === \'granted\';\n                }\n                else\n                    this.#approved = true;\n            }\n            catch {\n                this.#approved = false;\n            }\n        }\n        return this.#approved;\n    }\n    #handleDeviceMotion;\n    async start() {\n        const approved = await this.approve();\n        if (!approved)\n            return false;\n        window.addEventListener(\'devicemotion\', this.#handleDeviceMotion);\n        return true;\n    }\n    stop() {\n        window.removeEventListener(\'devicemotion\', this.#handleDeviceMotion);\n    }\n}\n////////////////////////////////////////////////////////////////////////////////\n// Use:\nconst shake = new Shake({ threshold: 15, timeout: 1000 });\nshake.addEventListener(\'shake\', ev => {\n    console.log(\'Shake!\', ev.detail.timeStamp, ev.detail.acceleration);\n});\nconst button = document.getElementById(\'start\');\nif (button) {\n    button.addEventListener(\'click\', async () => {\n        const approved = await shake.start();\n        const div = document.body.appendChild(document.createElement(\'div\'));\n        div.textContent = `Approved: ${String(approved)}`;\n        button.remove();\n    }, { once: true });\n}
Run Code Online (Sandbox Code Playgroud)\r\n
<button id="start">Approve</button>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n