一、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】注意:
我們不確定用戶忘哪個(gè)方向搖晃,所以X軸,Y軸,Z軸,三個(gè)方向我們都需要檢測
搖一搖這個(gè)功能一般我們是在用戶停止搖動之后才去做某些事情
devicemotion 執(zhí)行間隔特別的小,可以到達(dá)幾毫米,但是從性能上來說,我們并不需要這么小的執(zhí)行間隔,所以我們增加一個(gè)節(jié)流函數(shù)
上面講的兼容處理都需要加上
【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書籍資料