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

Posted

技术标签:

【中文标题】使用 Javascript 检测抖动事件,适用于所有主要浏览器/设备(iOS、Android)【英文标题】:Detect shake event with Javascript, with all major browsers/devices (iOS, Android) 【发布时间】:2021-12-31 18:28:59 【问题描述】:

我已经阅读了javascript. Listen for iPhone shake event? 和Detecting shaking in 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>

不幸的是,这似乎不适用于最新的 ios 设备,this issue 表明应该为最新的 iOS 版本授予特殊权限。请注意,代码from here 在库shake.js 中不易使用。

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

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

【问题讨论】:

Apple 向您收取在其操作系统上开发应用程序的费用。他们的操作系统被锁定,他们的浏览器可以支持他们想要的。换句话说,您需要联系摇动开发人员以获得最新的 shim 支持,或者继续在 iOS 中构建您的应用程序,您将获得所有支持。 :) "crossbrowser and crossdevice":我不知道这是什么意思。您能否提供您想要支持的特定浏览器和特定设备的列表? @jsejcksn 我的意思是:在主要浏览器(Firefox、Chrome、Safari)和主要移动设备(iOS、android)上工作。我编辑了问题和标题以包含此内容。 @Basj (1) 什么是“等”在浏览器中,您希望支持每个列出的浏览器的哪些版本? (2) 什么是“等”在设备中,您希望支持每个列出的操作系统的哪些版本? @Basj ????这缩小了可能的组合相当 【参考方案1】:

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

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

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

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

TS Playground

function createEvent <Type extends string, Detail>(
  type: Type,
  detail: Detail,
): CustomEvent<Detail> & type: Type 
  return new CustomEvent(type, detail) as CustomEvent<Detail> & type: Type;


function getMaxAcceleration (event: DeviceMotionEvent): number 
  let max = 0;
  if (event.acceleration) 
    for (const key of ['x', 'y', 'z'] as const) 
      const value = Math.abs(event.acceleration[key] ?? 0);
      if (value > max) max = value;
    
  
  return max;


export type ShakeEventData = DeviceMotionEvent;
export type ShakeEvent = CustomEvent<ShakeEventData> & type: 'shake';
export type ShakeEventListener = (event: ShakeEvent) => void;

export type ShakeOptions = 
  /**
   * Minimum acceleration needed to dispatch an event:
   * meters per second squared (m/s²).
   *
   * https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/acceleration
   */
  threshold: number;
  /**
   * After a shake event is dispatched, subsequent events will not be dispatched
   * until after a duration greater than or equal to this value (milliseconds).
   */
  timeout: number;
;

export class Shake extends EventTarget 
  #approved?: boolean;
  #threshold: ShakeOptions['threshold'];
  #timeout: ShakeOptions['timeout'];
  #timeStamp: number;

  constructor (options?: Partial<ShakeOptions>) 
    super();
    const 
      threshold = 15,
      timeout = 1000,
     = options ?? ;
    this.#threshold = threshold;
    this.#timeout = timeout;
    this.#timeStamp = timeout * -1;
  
  
  // @ts-ignore
  addEventListener (
    type: 'shake',
    listener: ShakeEventListener | null,
    options?: boolean | AddEventListenerOptions
  ): void 
    type Arg1 = Parameters<EventTarget['addEventListener']>[1];
    super.addEventListener(type, listener as Arg1, options);
  

  dispatchEvent (event: ShakeEvent): boolean 
    return super.dispatchEvent(event);
  

  // @ts-ignore
  removeEventListener (
    type: 'shake',
    callback: ShakeEventListener | null,
    options?: EventListenerOptions | boolean
  ): void 
    type Arg1 = Parameters<EventTarget['removeEventListener']>[1];
    super.removeEventListener(type, callback as Arg1, options);
  

  async approve (): Promise<boolean> 
    if (typeof this.#approved === 'undefined') 
      if (!('DeviceMotionEvent' in window)) return this.#approved = false;
      try 
        type PermissionRequestFn = () => Promise<PermissionState>;
        type DME = typeof DeviceMotionEvent &  requestPermission: PermissionRequestFn ;
        if (typeof (DeviceMotionEvent as DME).requestPermission === 'function') 
          const permissionState = await (DeviceMotionEvent as DME).requestPermission();
          this.#approved = permissionState === 'granted';
        
        else this.#approved = true;
      
      catch 
        this.#approved = false;
      
    
    return this.#approved;
  

  #handleDeviceMotion = (event: DeviceMotionEvent): void => 
    const diff = event.timeStamp - this.#timeStamp;
    if (diff < this.#timeout) return;
    const accel = getMaxAcceleration(event);
    if (accel < this.#threshold) return;
    this.#timeStamp = event.timeStamp;
    this.dispatchEvent(createEvent('shake', event));
  ;

  async start (): Promise<boolean> 
    const approved = await this.approve();
    if (!approved) return false;
    window.addEventListener('devicemotion', this.#handleDeviceMotion);
    return true;
  

  stop (): void 
    window.removeEventListener('devicemotion', this.#handleDeviceMotion);
  

这样使用:

const shake = new Shake(threshold: 15, timeout: 1000);

shake.addEventListener('shake', ev => 
  console.log('Shake!', ev.detail.timeStamp, ev.detail.acceleration);
);

// Then, in response to a user-initiated event:
const approved = await shake.start();

我不确定 SO sn-p 环境是否会导致演示问题,但我已经包含了来自 TS Playground 链接的已编译 JS,以防万一:

"use strict";
function createEvent(type, detail) 
    return new CustomEvent(type,  detail );

function getMaxAcceleration(event) 
    let max = 0;
    if (event.acceleration) 
        for (const key of ['x', 'y', 'z']) 
            const value = Math.abs(event.acceleration[key] ?? 0);
            if (value > max)
                max = value;
        
    
    return max;

class Shake extends EventTarget 
    constructor(options) 
        super();
        this.#handleDeviceMotion = (event) => 
            const diff = event.timeStamp - this.#timeStamp;
            if (diff < this.#timeout)
                return;
            const accel = getMaxAcceleration(event);
            if (accel < this.#threshold)
                return;
            this.#timeStamp = event.timeStamp;
            this.dispatchEvent(createEvent('shake', event));
        ;
        const  threshold = 15, timeout = 1000,  = options ?? ;
        this.#threshold = threshold;
        this.#timeout = timeout;
        this.#timeStamp = timeout * -1;
    
    #approved;
    #threshold;
    #timeout;
    #timeStamp;
    // @ts-ignore
    addEventListener(type, listener, options) 
        super.addEventListener(type, listener, options);
    
    dispatchEvent(event) 
        return super.dispatchEvent(event);
    
    // @ts-ignore
    removeEventListener(type, callback, options) 
        super.removeEventListener(type, callback, options);
    
    async approve() 
        if (typeof this.#approved === 'undefined') 
            if (!('DeviceMotionEvent' in window))
                return this.#approved = false;
            try 
                if (typeof DeviceMotionEvent.requestPermission === 'function') 
                    const permissionState = await DeviceMotionEvent.requestPermission();
                    this.#approved = permissionState === 'granted';
                
                else
                    this.#approved = true;
            
            catch 
                this.#approved = false;
            
        
        return this.#approved;
    
    #handleDeviceMotion;
    async start() 
        const approved = await this.approve();
        if (!approved)
            return false;
        window.addEventListener('devicemotion', this.#handleDeviceMotion);
        return true;
    
    stop() 
        window.removeEventListener('devicemotion', this.#handleDeviceMotion);
    

////////////////////////////////////////////////////////////////////////////////
// Use:
const shake = new Shake( threshold: 15, timeout: 1000 );
shake.addEventListener('shake', ev => 
    console.log('Shake!', ev.detail.timeStamp, ev.detail.acceleration);
);
const button = document.getElementById('start');
if (button) 
    button.addEventListener('click', async () => 
        const approved = await shake.start();
        const div = document.body.appendChild(document.createElement('div'));
        div.textContent = `Approved: $String(approved)`;
        button.remove();
    ,  once: true );
&lt;button id="start"&gt;Approve&lt;/button&gt;

【讨论】:

【参考方案2】:

查看这些链接:

Detect a shake in iOS Safari with Javascript?

https://github.com/lhagan/jquery.ios-shake

这第二个库基本上用作:

<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
<title>Detect shake in phone using Jquery</title>
<script type="text/javascript" src="jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="jquery.ios-shake.js"></script>
<script type="text/javascript">
$(document).ready(function() 
$.shake(
callback: function() 
alert("Please upvote my answer!");

);
);
</script>
</head>
<body>
<div id="content">
<h1>Detecting Phone shake using jQuery</h1>
</div>
<div id="welcome">Shake your phone to get the alert</div>
</body>
</html>

【讨论】:

谢谢,但我正在寻找一个没有 jquery 的纯 Javascript 解决方案。 而且这些库已经有 8 年以上的历史了,此时,iOS 没有什么特别的要求。如今,从 iOS 13 开始,需要特殊权限才能访问加速度计。这是问题中应该解决的问题。谢谢!

以上是关于使用 Javascript 检测抖动事件,适用于所有主要浏览器/设备(iOS、Android)的主要内容,如果未能解决你的问题,请参考以下文章

CSS圆角适用于所有没有图像和javascript的浏览器

CSS圆角适用于所有没有图像和javascript的浏览器

使用 react-native-sensors 检测抖动设备事件?

如何配置我的应用程序以检测抖动运动事件?

javascript touchstart事件防抖动#js #event

javascript touchstart事件防抖动#js #event