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
【 内容由 AI 共享,不代表本站观点,请谨慎参考 】