H5設(shè)備運(yùn)動事件 DeviceMotionEvent 實(shí)現(xiàn)搖一搖功能

一、DeviceMotion介紹

在 window 對象中存在一個(gè)專門的事件 devicemotion,它封裝了運(yùn)動傳感器數(shù)據(jù)的事件,可以用來監(jiān)聽設(shè)備的加速度變化等信息。devicemotion事件對象有acceleration、accelerationIncludingGravity、rotationRate和interval四個(gè)屬性

    window.addEventListener("devicemotion", (e) => {
      console.log(e)
    });

【1】acceleration(加速度)、accelerationIncludingGravity(重力加速度)

acceleration和accelerationIncludingGravity屬性都包含三個(gè)軸:X軸、Y軸、Z軸

  • acceleration:提供了設(shè)備在X,Y,Z軸方向上加速度的對象。加速度的單位為 m/s2

  • accelarationIncludingGravity:提供了設(shè)備在X,Y,Z軸方向上帶重力的加速度的對象。加速度的單位為 m/s2

    window.addEventListener("devicemotion", (e) => {
        let { acceleration, accelerationIncludingGravity } = e;

        let { x, y, z } = acceleration; //手機(jī)目前的移動速度 -- 加速度
        console.log(x,y,z);

        let { x:x2, y:y2, z:z2 } = acceleration; // 檢測手機(jī)的移動速度 和 現(xiàn)在每個(gè)方向受到的重力加速度
        console.log(x2,y2,z2);
    });

【2】rotationRate(旋轉(zhuǎn)速度)

提供了設(shè)備在 alpha,beta, gamma軸方向上旋轉(zhuǎn)的速率的對象。旋轉(zhuǎn)速率的單位為度每秒

  • alpha: 設(shè)備沿著垂直屏幕的軸的旋轉(zhuǎn)速率
  • beta: 設(shè)備沿著屏幕左至右方向的軸的旋轉(zhuǎn)速率
  • gamma: 設(shè)備沿著屏幕下至上方向的軸的旋轉(zhuǎn)速率

【3】interval(時(shí)間間隔)

表示從設(shè)備獲取數(shù)據(jù)的間隔時(shí)間,單位是毫秒

二、DeviceMotion 使用注意事項(xiàng)

【1】 使用 acceleration 時(shí),IOS下數(shù)值和安卓的數(shù)值剛好相反

安卓手機(jī):向左移動,x軸得到一個(gè)速度值為 20;向右移動,x軸得到一個(gè)速度值為 -20

IOS手機(jī):向左移動,x軸得到一個(gè)速度值為 -20;向右移動,x軸得到一個(gè)速度值為 20

Y軸和Z軸同理,所以我們代碼需要處理一下同時(shí)兼容安卓手機(jī)和IOS手機(jī)

    // 判斷是否是IOS設(shè)備,true代表IOS,false代表非IOS
    function getIos() {
      let u = window.navigator.userAgent;
      return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
    }

    window.addEventListener("devicemotion", (e) => {
      let { acceleration } = e;
      let { x, y, z } = acceleration;
      // 如果是IOS設(shè)備,xyz取反
      if (getIos()) {
        x = -x;
        y = -y;
        z = -z;
      }
    });

【2】IOS下的https要求

在 ios 11(包括目前的一些安卓機(jī)) 之后,如果想要在頁面中使用陀螺儀相關(guān)的一些事件,必須使用 https 協(xié)議

【3】IOS 用戶關(guān)閉了 "動作與方向訪問"

在 IOS 12.2 之后,用戶可以在手機(jī)的設(shè)置功能中關(guān)閉掉 "動作與方向訪問" ,如下圖所示。目前為止,開發(fā)人員有沒有有效的辦法直接獲取到用戶是否關(guān)閉了 "動作與方向訪問"

解決辦法:我們先通過 window.DeviceMotionEvent 排除掉不支持獲取加速度的設(shè)備,如果該設(shè)備支持就會有window.DeviceMotionEvent 這個(gè)屬性。設(shè)備如果支持 window.DeviceMotionEvent,開始一個(gè)事件監(jiān)測(這里注意加速度獲取時(shí)特別敏感的哪怕我們認(rèn)為我們的手機(jī)靜止了也會有一些加速給它,所以devicemotion這個(gè)事件會時(shí)時(shí)觸發(fā),觸發(fā)間隔極?。?,如果該事件沒有執(zhí)行,說明用戶關(guān)閉了動作和方向的權(quán)限。

    if (!window.DeviceMotionEvent) {
      alert("您的設(shè)備不支持DeviceMotion");
    } else {
      let timer = setTimeout(function () {
        alert('請?jiān)谠O(shè)置中開啟您設(shè)備的"動作和方向訪問"權(quán)限,否則您將無法使用本應(yīng)用');
      }, 100);
      window.addEventListener("devicemotion", (e) => {
        clearTimeout(timer);
      }, { once: true });
    }

【4】IOS 13 的用戶權(quán)限請求

IOS 13 之后新增的一個(gè)方法用來獲取用戶權(quán)限,獲取到用戶權(quán)限之后,我們就可以來檢測加速度了,但是注意該方法是 IOS 13 之后才有的,在 IOS 13 之前這么寫的話,DeviceMotionEvent下并沒有requestPermission方法就會報(bào)錯(cuò)

     DeviceMotionEvent.requestPermission().then(permissionState => {
        if (permissionState === 'granted') {
          window.addEventListener('devicemotion', () => { })
        }
      }).catch((err) => {

      });

三、搖一搖功能實(shí)現(xiàn)

【1】實(shí)現(xiàn)原理:當(dāng)我們在晃動手機(jī)時(shí),手機(jī)本身會產(chǎn)生一個(gè)加速度。我們可以截取手機(jī)的兩次加速度進(jìn)行比較,中間會有一個(gè)差值,當(dāng)差值大于某個(gè)幅度時(shí)就可以認(rèn)定用戶進(jìn)行了搖一搖這個(gè)操作。

【2】注意:

  1. 我們不確定用戶忘哪個(gè)方向搖晃,所以X軸,Y軸,Z軸,三個(gè)方向我們都需要檢測

  2. 搖一搖這個(gè)功能一般我們是在用戶停止搖動之后才去做某些事情

  3. devicemotion 執(zhí)行間隔特別的小,可以到達(dá)幾毫米,但是從性能上來說,我們并不需要這么小的執(zhí)行間隔,所以我們增加一個(gè)節(jié)流函數(shù)

  4. 上面講的兼容處理都需要加上

【3】代碼

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <button id="startBtn">開啟搖一搖</button>
  <button id="closeBtn">關(guān)閉搖一搖</button>
  <script>
    /*
      setDeviceMotion 添加陀螺儀監(jiān)控
      參數(shù): cb devicemotion的事件處理函數(shù); errCb 不支持devicemotion時(shí)的處理回調(diào)
    */
    function setDeviceMotion(cb, errCb) {
      if (!window.DeviceMotionEvent) {
        errCb("設(shè)備不支持DeviceMotion");
        return;
      }
      if (typeof DeviceMotionEvent.requestPermission === 'function') {
        // IOS13 設(shè)備
        DeviceMotionEvent.requestPermission()
          .then(permissionState => {
            if (permissionState === 'granted') {
              window.addEventListener('devicemotion', cb);
            }
          })
          .catch((err) => {
            errCb("用戶未允許權(quán)限");
          });
      } else {
        // 其他支持加速度檢測的系統(tǒng)
        let timer = setTimeout(function () {
          errCb("用戶未開啟權(quán)限");
        }, 1000);
        window.addEventListener("devicemotion", (e) => {
          clearTimeout(timer);
        }, { once: true });
        window.addEventListener("devicemotion", cb);
      }
    }

    /*
        throttle 節(jié)流函數(shù)
        參數(shù):
            fn 要節(jié)流的函數(shù)
            interval 節(jié)流間隔時(shí)間
            start 是否在節(jié)流開始時(shí)執(zhí)行 (true在開始時(shí)執(zhí)行,false在結(jié)束時(shí)執(zhí)行)
        返回值:
            經(jīng)過節(jié)流處理的函數(shù)
    */
    function throttle(fn, interval = 200, start = true) {
      if (typeof fn !== "function") {
        return console.error("請傳入一個(gè)函數(shù)");
      }
      let timer = 0;
      return function (...arg) {
        let _this = this;
        if (timer) {
          return;
        }
        start && fn.apply(_this, arg);
        timer = setTimeout(() => {
          (!start) && fn.apply(_this, arg);
          timer = 0;
        }, interval);
      }
    }

    /*
        addShake 添加搖一搖功能
        參數(shù):cbShake 類型 fn 當(dāng)用戶進(jìn)行了搖一搖之后要做的事情
        返回值:shakeIndex 開啟的第幾個(gè)搖一搖功能的索引,用來刪除監(jiān)聽     
    */
    function addShake(cbShake) {
      const maxRange = 60; //當(dāng)用戶的兩次加速度差值大于這個(gè)幅度,判定用戶進(jìn)行了搖一搖功能
      const minRange = 10; //當(dāng)用戶的兩次加速度差值小于這個(gè)幅度,判定用戶停止搖動手機(jī)
      let isShake = false; //記錄用戶是否搖動手機(jī)
      let lastX = 0,
          lastY = 0,
          lastZ = 0;
      function toShake(e) {
        let motion = e.acceleration;
        let { x, y, z } = motion;
        let range = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ);
        if (range > maxRange) {//用戶進(jìn)行了搖一搖
          isShake = true;
        }
        if (range < minRange && isShake) { // 停止搖一搖
          cbShake(e);
          isShake = false;
        }
        lastX = x;
        lastY = y;
        lastZ = z;
      }
      if (!window.shakeEvent) { //建立 shakeEvent 存儲所有的搖一搖的處理函數(shù),方便一會取消
        window.shakeEvent = [];
      }
      toShake = throttle(toShake);
      window.shakeEvent.push(toShake);
      setDeviceMotion(toShake, (errMessage) => {
        alert(errMessage)
      })
      return window.shakeEvent.length - 1;//返回該次搖一搖處理的索引
    }

    /*
        刪除搖一搖監(jiān)聽
        cbShake 類型 fn 當(dāng)用戶進(jìn)行了搖一搖之后要做的事情
    */
    function remveShake(shakeIndex) {
      window.removeEventListener("devicemotion", window.shakeEvent[shakeIndex]);
    }

    // 調(diào)用搖一搖
    {
      let startBtn = document.querySelector("#startBtn");
      let closeBtn = document.querySelector("#closeBtn");
      let isStartShake = false;
      let shakeIndex;
      // 再次強(qiáng)調(diào) IOS 13.3 需要用戶觸發(fā),再能開啟搖一搖 
      startBtn.addEventListener("touchend", () => {
        if (isStartShake) return;
        isStartShake = true;
        shakeIndex = addShake(() => {
          alert("您進(jìn)行了搖一搖")
        })
      });
      closeBtn.addEventListener("touchend", () => {
        if (!isStartShake) return;
        isStartShake = false;
        remveShake(shakeIndex);
      });

    }
  </script>
</body>

</html>

文章每周持續(xù)更新,可以微信搜索「 前端大集錦 」第一時(shí)間閱讀,回復(fù)【視頻】【書籍】領(lǐng)取200G視頻資料和30本PDF書籍資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 事件是什么,可以用來做什么,什么時(shí)候用到它? 事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間。JavaScr...
    茂茂愛吃魚閱讀 1,612評論 0 16
  • 本章內(nèi)容:理解事件流、使用事件處理程序、不同的事件類型 JavaScript與HTML之間的交互是通過事件實(shí)現(xiàn)的。...
    了凡和纖風(fēng)閱讀 458評論 0 0
  • orientationchange 事件Safari 蘋果公司為移動 Safari 中添加了 orientatio...
    武_b070閱讀 688評論 0 0
  • iOS傳感器篇:CoreMotion初探 轉(zhuǎn)載http://m.itdecent.cn/p/9f915876...
    老四似曾相識閱讀 4,192評論 0 1
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,916評論 0 11

友情鏈接更多精彩內(nèi)容