9.2 设备方向与运动检测
9.2 设备方向与运动检测
现代设备传感器API为Web开发者提供了访问设备物理方向、运动状态和环境信息的能力,这些功能在游戏、VR/AR应用和交互式体验中至关重要。本节将全面介绍DeviceOrientation和DeviceMotion API的使用方法与实际应用。
设备方向检测(DeviceOrientation)
1. 基本方向事件
// 检查API支持情况
if (!window.DeviceOrientationEvent) {
alert("您的浏览器不支持设备方向检测");
return;
}
// 注册方向事件监听
window.addEventListener('deviceorientation', (event) => {
const { alpha, beta, gamma } = event;
// alpha: 0-360度,绕Z轴旋转(指南针方向)
// beta: -180到180度,绕X轴旋转(前后倾斜)
// gamma: -90到90度,绕Y轴旋转(左右倾斜)
updateCompass(alpha);
updateTilt(beta, gamma);
console.log(`方向: α=${alpha.toFixed(1)}° β=${beta.toFixed(1)}° γ=${gamma.toFixed(1)}°`);
});
// 请求权限(iOS 13+需要)
function requestOrientationPermission() {
if (typeof DeviceOrientationEvent !== 'undefined' &&
typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
}
})
.catch(console.error);
} else {
// 其他浏览器直接添加监听
window.addEventListener('deviceorientation', handleOrientation);
}
}
// 用户交互后触发权限请求
document.getElementById('start-btn').addEventListener('click', requestOrientationPermission);
2. 3D空间方向可视化
class OrientationVisualizer {
constructor() {
this.cube = document.getElementById('orientation-cube');
this.initialOrientation = null;
}
start() {
window.addEventListener('deviceorientation', this.handleOrientation.bind(this));
}
handleOrientation(event) {
if (!this.initialOrientation) {
this.initialOrientation = {
alpha: event.alpha,
beta: event.beta,
gamma: event.gamma
};
}
// 计算相对于初始方向的偏移
const alpha = event.alpha - this.initialOrientation.alpha;
const beta = event.beta - this.initialOrientation.beta;
const gamma = event.gamma - this.initialOrientation.gamma;
// 应用3D变换
this.cube.style.transform = `
rotateZ(${alpha}deg)
rotateX(${beta}deg)
rotateY(${gamma}deg)
`;
}
}
// 使用示例
const visualizer = new OrientationVisualizer();
visualizer.start();
设备运动检测(DeviceMotion)
1. 加速度与陀螺仪数据
// 检查API支持
if (!window.DeviceMotionEvent) {
alert("您的浏览器不支持设备运动检测");
return;
}
// 请求权限(iOS 13+)
function requestMotionPermission() {
if (typeof DeviceMotionEvent !== 'undefined' &&
typeof DeviceMotionEvent.requestPermission === 'function') {
DeviceMotionEvent.requestPermission()
.then(response => {
if (response === 'granted') {
startMotionTracking();
}
})
.catch(console.error);
} else {
startMotionTracking();
}
}
function startMotionTracking() {
window.addEventListener('devicemotion', (event) => {
const { acceleration, accelerationIncludingGravity, rotationRate, interval } = event;
// acceleration: 不含重力的加速度(x,y,z m/s²)
// accelerationIncludingGravity: 含重力的加速度
// rotationRate: 旋转速率(alpha,beta,gamma °/s)
updateMotionUI({
x: accelerationIncludingGravity.x,
y: accelerationIncludingGravity.y,
z: accelerationIncludingGravity.z,
rotation: rotationRate
});
});
}
// 用户点击触发
document.getElementById('motion-btn').addEventListener('click', requestMotionPermission);
2. 计步器实现
class StepCounter {
constructor() {
this.lastAcceleration = null;
this.stepCount = 0;
this.threshold = 10; // 加速度变化阈值
this.lastStepTime = 0;
this.minStepInterval = 300; // 最小步间隔(ms)
}
start() {
window.addEventListener('devicemotion', this.detectStep.bind(this));
}
detectStep(event) {
const accel = event.accelerationIncludingGravity;
const now = Date.now();
if (!this.lastAcceleration) {
this.lastAcceleration = accel;
return;
}
// 计算加速度变化量
const delta = Math.sqrt(
Math.pow(accel.x - this.lastAcceleration.x, 2) +
Math.pow(accel.y - this.lastAcceleration.y, 2) +
Math.pow(accel.z - this.lastAcceleration.z, 2)
);
// 检测步伐(满足阈值和时间间隔)
if (delta > this.threshold && (now - this.lastStepTime) > this.minStepInterval) {
this.stepCount++;
this.lastStepTime = now;
updateStepDisplay(this.stepCount);
}
this.lastAcceleration = accel;
}
}
// 使用示例
const pedometer = new StepCounter();
pedometer.start();
高级应用场景
1. 手机控制3D模型
class DeviceController {
constructor(model3D) {
this.model = model3D;
this.orientation = { alpha: 0, beta: 0, gamma: 0 };
this.motion = { x: 0, y: 0, z: 0 };
this.initEventListeners();
}
initEventListeners() {
window.addEventListener('deviceorientation', (event) => {
this.orientation = {
alpha: event.alpha || 0,
beta: event.beta || 0,
gamma: event.gamma || 0
};
this.updateModel();
});
window.addEventListener('devicemotion', (event) => {
const accel = event.accelerationIncludingGravity;
this.motion = {
x: accel.x || 0,
y: accel.y || 0,
z: accel.z || 0
};
this.updateModel();
});
}
updateModel() {
// 使用方向数据控制旋转
this.model.rotation.set(
THREE.MathUtils.degToRad(this.orientation.beta),
THREE.MathUtils.degToRad(this.orientation.alpha),
THREE.MathUtils.degToRad(this.orientation.gamma)
);
// 使用运动数据控制位置
this.model.position.x = this.motion.x * 0.1;
this.model.position.y = this.motion.y * 0.1;
// 触发渲染更新
renderer.render();
}
}
// Three.js集成示例
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
const cube = new THREE.Mesh(/* 立方体几何体 */);
const controller = new DeviceController(cube);
2. 虚拟现实控制器
class VRController {
constructor() {
this.position = { x: 0, y: 0, z: 0 };
this.rotation = { alpha: 0, beta: 0, gamma: 0 };
this.buttons = [false, false, false];
this.setupEventListeners();
}
setupEventListeners() {
// 方向控制
window.addEventListener('deviceorientation', (event) => {
this.rotation = {
alpha: event.alpha,
beta: event.beta,
gamma: event.gamma
};
});
// 位置追踪
window.addEventListener('devicemotion', (event) => {
const accel = event.acceleration;
if (accel) {
this.position.x += accel.x * 0.01;
this.position.y += accel.y * 0.01;
this.position.z += accel.z * 0.01;
}
});
// 屏幕触摸作为按钮
document.addEventListener('touchstart', (e) => {
this.buttons[0] = true; // 主按钮
});
document.addEventListener('touchend', () => {
this.buttons[0] = false;
});
}
getState() {
return {
position: { ...this.position },
rotation: { ...this.rotation },
buttons: [...this.buttons]
};
}
}
// 在游戏循环中使用
const controller = new VRController();
function gameLoop() {
const state = controller.getState();
updatePlayer(state);
requestAnimationFrame(gameLoop);
}
gameLoop();
性能优化与调试
1. 节流与数据平滑
class SensorProcessor {
constructor(smoothFactor = 0.2) {
this.smoothFactor = smoothFactor;
this.smoothedValues = { alpha: 0, beta: 0, gamma: 0 };
this.lastUpdate = 0;
this.updateInterval = 50; // ms
}
handleOrientation(event) {
const now = Date.now();
if (now - this.lastUpdate < this.updateInterval) return;
this.lastUpdate = now;
// 应用指数平滑滤波
this.smoothedValues = {
alpha: this.smooth(event.alpha, this.smoothedValues.alpha),
beta: this.smooth(event.beta, this.smoothedValues.beta),
gamma: this.smooth(event.gamma, this.smoothedValues.gamma)
};
updateUI(this.smoothedValues);
}
smooth(newValue, oldValue) {
return oldValue + this.smoothFactor * (newValue - oldValue);
}
}
// 使用示例
const processor = new SensorProcessor();
window.addEventListener('deviceorientation', processor.handleOrientation.bind(processor));
2. 传感器校准
class CalibratedOrientation {
constructor() {
this.calibration = { alpha: 0, beta: 0, gamma: 0 };
this.isCalibrating = false;
}
startCalibration() {
this.isCalibrating = true;
this.calibrationSamples = [];
// 收集5秒的样本数据
const calibrationListener = (event) => {
this.calibrationSamples.push({
alpha: event.alpha,
beta: event.beta,
gamma: event.gamma
});
};
window.addEventListener('deviceorientation', calibrationListener);
setTimeout(() => {
window.removeEventListener('deviceorientation', calibrationListener);
this.calculateCalibration();
this.isCalibrating = false;
}, 5000);
}
calculateCalibration() {
const sum = this.calibrationSamples.reduce((acc, sample) => {
acc.alpha += sample.alpha;
acc.beta += sample.beta;
acc.gamma += sample.gamma;
return acc;
}, { alpha: 0, beta: 0, gamma: 0 });
const count = this.calibrationSamples.length;
this.calibration = {
alpha: sum.alpha / count,
beta: sum.beta / count,
gamma: sum.gamma / count
};
}
getCalibratedOrientation(event) {
return {
alpha: event.alpha - this.calibration.alpha,
beta: event.beta - this.calibration.beta,
gamma: event.gamma - this.calibration.gamma
};
}
}
// 使用示例
const calibrator = new CalibratedOrientation();
document.getElementById('calibrate-btn').addEventListener('click', () => {
calibrator.startCalibration();
});
window.addEventListener('deviceorientation', (event) => {
if (!calibrator.isCalibrating) {
const calibrated = calibrator.getCalibratedOrientation(event);
// 使用校准后的数据...
}
});
跨浏览器兼容方案
1. 特性检测与回退
class SensorManager {
constructor() {
this.supported = {
orientation: 'DeviceOrientationEvent' in window,
motion: 'DeviceMotionEvent' in window
};
this.orientation = { alpha: 0, beta: 0, gamma: 0 };
this.motion = { x: 0, y: 0, z: 0 };
this.init();
}
init() {
if (this.supported.orientation) {
window.addEventListener('deviceorientation', (event) => {
this.orientation = {
alpha: event.alpha || 0,
beta: event.beta || 0,
gamma: event.gamma || 0
};
});
} else {
console.warn('设备方向API不支持,使用键盘控制');
this.setupKeyboardFallback();
}
if (this.supported.motion) {
window.addEventListener('devicemotion', (event) => {
const accel = event.accelerationIncludingGravity || { x: 0, y: 0, z: 0 };
this.motion = {
x: accel.x || 0,
y: accel.y || 0,
z: accel.z || 0
};
});
}
}
setupKeyboardFallback() {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp': this.orientation.beta -= 5; break;
case 'ArrowDown': this.orientation.beta += 5; break;
case 'ArrowLeft': this.orientation.alpha -= 5; break;
case 'ArrowRight': this.orientation.alpha += 5; break;
}
});
}
getOrientation() {
return { ...this.orientation };
}
getMotion() {
return { ...this.motion };
}
}
2. iOS特定处理
// iOS 13+权限请求封装
function setupSensors() {
// 方向传感器
if (typeof DeviceOrientationEvent !== 'undefined' &&
typeof DeviceOrientationEvent.requestPermission === 'function') {
document.getElementById('start-btn').style.display = 'block';
document.getElementById('start-btn').addEventListener('click', () => {
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
startOrientationTracking();
}
})
.catch(console.error);
DeviceMotionEvent.requestPermission()
.then(response => {
if (response === 'granted') {
startMotionTracking();
}
})
.catch(console.error);
});
} else {
// 其他平台直接启动
startOrientationTracking();
startMotionTracking();
}
}
// 页面加载后初始化
window.addEventListener('DOMContentLoaded', setupSensors);
安全与隐私实践
1. 权限管理策略
async function checkSensorPermissions() {
const permissions = {};
if (navigator.permissions) {
try {
// 检查方向传感器权限
const orientationStatus = await navigator.permissions.query({
name: 'accelerometer'
});
permissions.orientation = orientationStatus.state;
// 检查运动传感器权限
const motionStatus = await navigator.permissions.query({
name: 'gyroscope'
});
permissions.motion = motionStatus.state;
} catch (e) {
console.warn('权限API不完全支持:', e);
}
}
return permissions;
}
// 根据权限状态显示UI
async function updatePermissionUI() {
const permissions = await checkSensorPermissions();
if (permissions.orientation === 'denied' || permissions.motion === 'denied') {
showPermissionWarning();
} else if (permissions.orientation === 'prompt' || permissions.motion === 'prompt') {
showPermissionRequest();
}
}
2. 数据访问控制
class PrivacySafeSensor {
constructor() {
this.enabled = false;
this.maxSampleRate = 10; // 最大采样率(Hz)
this.lastSampleTime = 0;
}
enable() {
return new Promise((resolve) => {
if (this.enabled) return resolve(true);
const onPermissionGranted = () => {
this.enabled = true;
resolve(true);
};
// iOS特殊处理
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(status => {
if (status === 'granted') onPermissionGranted();
else resolve(false);
});
} else {
onPermissionGranted();
}
});
}
getOrientation() {
if (!this.enabled) return null;
const now = Date.now();
if (now - this.lastSampleTime < 1000 / this.maxSampleRate) {
return null; // 限制采样率
}
this.lastSampleTime = now;
return this.currentOrientation;
}
start() {
window.addEventListener('deviceorientation', (event) => {
this.currentOrientation = {
alpha: Math.floor(event.alpha), // 降低精度
beta: Math.floor(event.beta),
gamma: Math.floor(event.gamma),
timestamp: Date.now()
};
});
}
}
设备方向与运动检测API为Web应用带来了丰富的交互可能性,开发时应注意:
- 始终考虑用户隐私,明确说明传感器数据用途
- 处理iOS等平台的特殊权限要求
- 实现适当的降级方案和兼容性处理
- 优化性能避免过度采样
- 对原始传感器数据进行适当的滤波和校准
通过合理使用这些API,开发者可以创建出沉浸式的设备物理交互体验,同时保持良好的用户体验和隐私保护。
#前端开发
分享于 2025-05-20
上一篇:9.1 地理位置 API
下一篇:9.3 全屏API