Device Orientation 事件

深入理解 Device Orientation 和 Device Motion 事件:加速度、旋转角速度、设备方向检测与交互应用。

Device Orientation 事件

一、设备传感器 API 概述

1.1 什么是设备方向事件

设备方向事件允许 Web 应用检测设备的物理方向和运动。这些数据来自设备内置的传感器:陀螺仪、指南针和加速度计。移动设备通常具备这些传感器,但桌面设备可能不具备。

// 监听设备方向变化
window.addEventListener('deviceorientation', (event) => {
  console.log('Alpha:', event.alpha); // Z轴旋转 0-360
  console.log('Beta:', event.beta);   // X轴旋转 -180 到 180
  console.log('Gamma:', event.gamma); // Y轴旋转 -90 到 90
});

1.2 相关事件

事件 描述 数据来源
deviceorientation 设备朝向变化 陀螺仪 + 指南针
deviceorientationabsolute 绝对方向变化 融合传感器
devicemotion 设备运动/加速度 加速度计

二、deviceorientation 事件

2.1 旋转角度

属性 描述 范围
alpha Z轴旋转(指南针方向) 0° - 360°
beta X轴旋转(前后倾斜) -180° - 180°
gamma Y轴旋转(左右倾斜) -90° - 90°
设备方向示意图:

Beta (X轴旋转)
    ↓
  +---+
  |   | ← 手机直立
  |   |
  +---+  alpha=0
    ↑
   手机背面

Gamma (Y轴旋转)
  +---+
  |   | ← 手机左右倾斜
  +---+
    ↙
  左边翘起为负,右边翘起为正

2.2 实际应用

// 3D 物体旋转控制
window.addEventListener('deviceorientation', (e) => {
  const { alpha, beta, gamma } = e;

  // 将角度转换为弧度
  const radAlpha = alpha * Math.PI / 180;
  const radBeta = beta * Math.PI / 180;
  const radGamma = gamma * Math.PI / 180;

  // 应用到 3D 物体
  cube.rotation.z = radAlpha;
  cube.rotation.x = radBeta;
  cube.rotation.y = radGamma;
});

2.3 WebXR 中的应用

// 在 WebXR 中使用设备方向
if ('DeviceOrientationEvent' in window) {
  window.addEventListener('deviceorientation', (e) => {
    // 校准设备方向
    const quaternion = e quaternion;

    // 应用到 XR 控制器
    controller.quaternion.set(
      quaternion.x,
      quaternion.y,
      quaternion.z,
      quaternion.w
    );
  });
}

三、devicemotion 事件

3.1 加速度数据

window.addEventListener('devicemotion', (event) => {
  const { x, y, z } = event.accelerationIncludingGravity;
  // acceleration:不含重力的加速度
  // accelerationIncludingGravity:含重力影响的加速度
  console.log(`Acceleration: ${x}, ${y}, ${z}`);
});

3.2 旋转速率

window.addEventListener('devicemotion', (event) => {
  const { alpha, beta, gamma } = event.rotationRate;
  console.log(`Rotation rate: α=${alpha}, β=${beta}, γ=${gamma}`);
});

3.3 摇动手势检测

class ShakeDetector {
  constructor(threshold = 15) {
    this.threshold = threshold;
    this.lastX = this.lastY = this.lastZ = 0;
    this.lastTime = Date.now();

    window.addEventListener('devicemotion', (e) => this.handleMotion(e));
  }

  handleMotion(event) {
    const { x, y, z } = event.accelerationIncludingGravity;
    const currentTime = Date.now();
    const timeDiff = currentTime - this.lastTime;

    if (timeDiff > 100) {
      const deltaX = Math.abs(x - this.lastX);
      const deltaY = Math.abs(y - this.lastY);
      const deltaZ = Math.abs(z - this.lastZ);

      const acceleration = (deltaX + deltaY + deltaZ) / timeDiff * 10000;

      if (acceleration > this.threshold) {
        this.onShake();
      }

      this.lastX = x;
      this.lastY = y;
      this.lastZ = z;
      this.lastTime = currentTime;
    }
  }

  onShake() {
    console.log('Device shaken!');
  }
}

四、权限请求

4.1 iOS 13+ 需要权限

// iOS 13+ 需要用户授权
async function requestOrientationPermission() {
  if (typeof DeviceOrientationEvent.requestPermission === 'function') {
    try {
      const permission = await DeviceOrientationEvent.requestPermission();
      if (permission === 'granted') {
        enableOrientation监听();
      }
    } catch (error) {
      console.error('Permission denied:', error);
    }
  } else {
    // 非 iOS 设备不需要请求权限
    enableOrientation监听();
  }
}

function enableOrientation监听() {
  window.addEventListener('deviceorientation', handleOrientation);
}

// 在用户交互中调用
document.querySelector('#start-btn').addEventListener('click', () => {
  requestOrientationPermission();
});

4.2 完整示例

class DeviceOrientationController {
  constructor() {
    this.beta = 0;
    this.gamma = 0;
    this.alpha = 0;
  }

  async init() {
    // iOS 13+ 需要权限
    if (typeof DeviceOrientationEvent.requestPermission === 'function') {
      const permission = await DeviceOrientationEvent.requestPermission();
      if (permission !== 'granted') {
        console.warn('Device orientation permission denied');
        return false;
      }
    }

    window.addEventListener('deviceorientation', (e) => this.handleOrientation(e));
    return true;
  }

  handleOrientation(event) {
    this.beta = event.beta || 0;   // X轴: -180 到 180
    this.gamma = event.gamma || 0; // Y轴: -90 到 90
    this.alpha = event.alpha || 0; // Z轴: 0 到 360
  }

  getQuaternion() {
    // 将欧拉角转换为四元数(用于 WebXR)
    const r = (this.alpha || 0) * Math.PI / 180;
    const p = (this.beta || 0) * Math.PI / 180;
    const y = (this.gamma || 0) * Math.PI / 180;

    const cy = Math.cos(y * 0.5);
    const sy = Math.sin(y * 0.5);
    const cp = Math.cos(p * 0.5);
    const sp = Math.sin(p * 0.5);
    const cr = Math.cos(r * 0.5);
    const sr = Math.sin(r * 0.5);

    return {
      w: cr * cp * cy + sr * sp * sy,
      x: sr * cp * cy - cr * sp * sy,
      y: cr * sp * cy + sr * cp * sy,
      z: cr * cp * sy - sr * sp * cy
    };
  }
}

五、安全与隐私

5.1 HTTPS 要求

// 设备方向事件需要安全上下文
if (window.isSecureContext) {
  console.log('Device orientation available');
} else {
  console.warn('Device orientation requires HTTPS');
}

5.2 权限层级

设备 权限要求
iOS 13+ 显式用户授权
Android 通常自动授予(少数情况需要)
桌面浏览器 模拟器/部分浏览器支持

六、面试高频问题

Q: deviceorientation 和 devicemotion 有什么区别?

回答要点:deviceorientation 报告设备朝向变化(alpha/beta/gamma 旋转角度),数据来自陀螺仪和指南针;devicemotion 报告设备运动/加速度(x/y/z加速度和旋转速率),数据来自加速度计。前者用于方向控制,后者用于运动检测。

Q: 为什么 iOS 13+ 需要请求设备方向权限?

回答要点:Apple 为保护用户隐私,将 DeviceOrientationEvent 列入了需要明确授权的 API。用户必须通过点击等交互行为触发权限请求,而不是页面加载时自动请求。

Q: 如何在 Web 应用中使用设备方向控制 3D 物体?

回答要点:监听 deviceorientation 事件获取 alpha/beta/gamma 角度,将这些角度转换为 3D 框架(如 Three.js)使用的旋转格式(通常是四元数或欧拉角),应用到物体或相机的 rotation 属性。


参考资料

延展阅读