Web 传感器

使用 Generic 传感器 API 访问设备上的传感器,例如加速度计、陀螺仪和磁力计。

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

目前,传感器数据在许多平台专用应用中得到了广泛使用,以实现沉浸式游戏、健身跟踪以及增强现实或虚拟现实等用例。如果能跨平台原生应用和 Web 应用之间的鸿沟,那不是很酷吗?欢迎使用适用于 Web 的 Generic Sensor API

什么是通用传感器 API?

通用传感器 API 是一组接口,用于将传感器设备公开给 Web 平台。该 API 由基本 Sensor 接口和一组基于该接口构建的具体传感器类组成。使用基接口可以简化具体传感器类的实现和规范流程。例如,请查看 Gyroscope 类。它超小了!核心功能由基接口指定,Gyroscope 仅通过三个表示角速度的属性对其进行扩展。

某些传感器类会与实际硬件传感器(例如加速度计或陀螺仪类)接口。这些传感器称为低级传感器。其他传感器(称为融合传感器)会合并来自多个低级传感器的数据,以显示脚本否则需要计算的信息。例如,AbsoluteOrientation 传感器会根据从加速度计、陀螺仪和磁力计获取的数据提供一个随时可用的 4 x 4 旋转矩阵。

您可能会认为网站平台已经提供传感器数据,您绝对是对的!例如,DeviceMotionDeviceOrientation 事件会公开动作传感器数据。那么,为什么我们需要新的 API?

与现有接口相比,通用传感器 API 具有诸多优势:

  • Generic Sensor API 是一种传感器框架,可通过新的传感器类轻松扩展,并且每个类都将保留通用接口。为一种传感器类型编写的客户端代码只需进行少量修改,即可用于另一种传感器类型!
  • 您可以配置传感器。例如,您可以设置适合应用需求的采样频率。
  • 您可以检测平台上是否有传感器。
  • 传感器读数具有高精度时间戳,可更好地与应用中的其他活动同步。
  • 传感器数据模型和坐标系已明确定义,让浏览器供应商能够实现可互操作的解决方案。
  • 基于通用传感器的接口不会绑定到 DOM(这意味着它们既不是 navigator 对象,也不是 window 对象),这为以后在 Service Worker 中使用该 API 或在无头 JavaScript 运行时(例如嵌入式设备)中实现该 API 创造了机会。
  • 安全和隐私是通用传感器 API 的首要考虑因素,与旧版传感器 API 相比,其安全性更高。与 Permissions API 进行了集成。
  • AccelerometerGyroscopeLinearAccelerationSensorAbsoluteOrientationSensorRelativeOrientationSensorMagnetometer 支持自动与屏幕坐标同步

可用的通用传感器 API

在撰写本文时,您可以试用多种传感器。

移动传感器

  • Accelerometer
  • Gyroscope
  • LinearAccelerationSensor
  • AbsoluteOrientationSensor
  • RelativeOrientationSensor
  • GravitySensor

环境传感器

  • AmbientLightSensor(位于 Chromium 中的 #enable-generic-sensor-extra-classes 标志后面)。
  • Magnetometer(位于 Chromium 中的 #enable-generic-sensor-extra-classes 标志后面)。

功能检测

对硬件 API 进行功能检测比较复杂,因为您需要同时检测浏览器是否支持相关接口以及设备是否具有相应的传感器。检查浏览器是否支持某个接口非常简单。(将 Accelerometer 替换为上文中提及的任何其他接口。)

if ('Accelerometer' in window) {
  // The `Accelerometer` interface is supported by the browser.
  // Does the device have an accelerometer, though?
}

为了获得真正有意义的特征检测结果,您还需要尝试连接到传感器。以下示例展示了如何执行此操作。

let accelerometer = null;
try {
  accelerometer = new Accelerometer({ frequency: 10 });
  accelerometer.onerror = (event) => {
    // Handle runtime errors.
    if (event.error.name === 'NotAllowedError') {
      console.log('Permission to access sensor was denied.');
    } else if (event.error.name === 'NotReadableError') {
      console.log('Cannot connect to the sensor.');
    }
  };
  accelerometer.onreading = (e) => {
    console.log(e);
  };
  accelerometer.start();
} catch (error) {
  // Handle construction errors.
  if (error.name === 'SecurityError') {
    console.log('Sensor construction was blocked by the Permissions Policy.');
  } else if (error.name === 'ReferenceError') {
    console.log('Sensor is not supported by the User Agent.');
  } else {
    throw error;
  }
}

polyfill

对于不支持通用传感器 API 的浏览器,可以使用 polyfill。借助此 polyfill,您可以仅加载相关传感器的实现。

// Import the objects you need.
import { Gyroscope, AbsoluteOrientationSensor } from './src/motion-sensors.js';

// And they're ready for use!
const gyroscope = new Gyroscope({ frequency: 15 });
const orientation = new AbsoluteOrientationSensor({ frequency: 60 });

这些传感器分别是什么?如何使用?

传感器可能需要进行简要介绍。如果您熟悉传感器,可以直接跳至实操编码部分。否则,我们来详细了解每个受支持的传感器。

加速度计和线性加速度传感器

加速度计传感器测量结果

Accelerometer 传感器可测量托管该传感器的设备在三个轴(X、Y 和 Z)上的加速度。此传感器是一种惯性传感器,这意味着当设备处于线性自由落体时,测得的总加速度为 0 m/s2;当设备平放在桌子上时,向上方向(Z 轴)的加速度将等于地球重力,即 g ≈ +9.8 m/s2,因为它测量的是桌子向上推设备的力。如果您向右推动设备,X 轴上的加速度为正;如果设备从右向左加速,则加速度为负。

加速度计可用于计步、运动感知或简单的设备方向等用途。通常,加速度计测量结果会与来自其他来源的数据结合使用,以创建融合传感器,例如屏幕方向传感器。

LinearAccelerationSensor 用于测量施加到托管传感器的设备的加速度,不包括重力的影响。当设备处于静止状态(例如平放在桌子上)时,传感器会测量三个轴上的加速度,结果约为 0 m/s2

重力传感器

用户已经可以通过手动检查 AccelerometerLinearAccelerometer 读数,手动派生接近重力传感器读数的读数,但这可能很麻烦,并且取决于这些传感器提供的值的准确性。Android 等平台可以在操作系统中提供重力读数,这在计算方面应该更经济,并且可以根据用户的硬件提供更准确的值,从 API 人体工学角度来看,也更易于使用。GravitySensor 会返回设备 X 轴、Y 轴和 Z 轴上因重力加速度而产生的加速度效应。

陀螺仪

陀螺仪传感器测量

Gyroscope 传感器测量设备本地 X、Y 和 Z 轴周围的角速度(以弧度/秒为单位)。大多数消费类设备都具有机械式 (MEMS) 陀螺仪,它们是惯性传感器,可根据惯性科里奥利力测量旋转率。MEMS 陀螺仪容易出现漂移,这是由传感器的引力灵敏度导致的,该灵敏度会使传感器的内部机械系统变形。陀螺仪以相对较高的频率振荡,例如因此,与其他传感器相比,10s kHz 可能会消耗更多电量。

屏幕方向传感器

绝对方向传感器测量结果

AbsoluteOrientationSensor 是一种融合传感器,用于测量设备相对于地球坐标系的旋转;而 RelativeOrientationSensor 则提供表示托管运动传感器的设备相对于静态参考坐标系的旋转的数据。

所有现代 3D JavaScript 框架都支持四元数旋转矩阵来表示旋转;不过,如果您直接使用 WebGL,OrientationSensor 为方便起见,同时具有 quaternion 属性populateMatrix() 方法。下面是一些代码段:

three.js

let torusGeometry = new THREE.TorusGeometry(7, 1.6, 4, 3, 6.3);
let material = new THREE.MeshBasicMaterial({ color: 0x0071c5 });
let torus = new THREE.Mesh(torusGeometry, material);
scene.add(torus);

// Update mesh rotation using quaternion.
const sensorAbs = new AbsoluteOrientationSensor();
sensorAbs.onreading = () => torus.quaternion.fromArray(sensorAbs.quaternion);
sensorAbs.start();

// Update mesh rotation using rotation matrix.
const sensorRel = new RelativeOrientationSensor();
let rotationMatrix = new Float32Array(16);
sensor_rel.onreading = () => {
  sensorRel.populateMatrix(rotationMatrix);
  torus.matrix.fromArray(rotationMatrix);
};
sensorRel.start();

BABYLON

const mesh = new BABYLON.Mesh.CreateCylinder('mesh', 0.9, 0.3, 0.6, 9, 1, scene);
const sensorRel = new RelativeOrientationSensor({ frequency: 30 });
sensorRel.onreading = () => mesh.rotationQuaternion.FromArray(sensorRel.quaternion);
sensorRel.start();

WebGL

// Initialize sensor and update model matrix when new reading is available.
let modMatrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
const sensorAbs = new AbsoluteOrientationSensor({ frequency: 60 });
sensorAbs.onreading = () => sensorAbs.populateMatrix(modMatrix);
sensorAbs.start();

// Somewhere in rendering code, update vertex shader attribute for the model
gl.uniformMatrix4fv(modMatrixAttr, false, modMatrix);

屏幕方向传感器支持各种应用场景,例如沉浸式游戏、增强现实和虚拟现实。

如需详细了解移动传感器、高级用例和要求,请参阅移动传感器说明文档

与屏幕坐标同步

默认情况下,空间传感器的读数在绑定到设备的局部坐标系中解析,而不将屏幕方向考虑在内。

设备坐标系
设备坐标系

不过,游戏或增强现实和虚拟现实等许多使用场景都需要在绑定到屏幕方向的坐标系中解析传感器读数。

屏幕坐标系
屏幕坐标系

以前,必须使用 JavaScript 将传感器读数重新映射到屏幕坐标。这种方法效率不高,并且会大大增加 Web 应用代码的复杂性;Web 应用必须监控屏幕方向变化并对传感器读数执行坐标转换,这对于欧拉角或四元数来说并不是一件容易的事。

Generic Sensor API 提供了更简单、更可靠的解决方案!本地坐标系可针对所有已定义的空间传感器类进行配置:AccelerometerGyroscopeLinearAccelerationSensorAbsoluteOrientationSensorRelativeOrientationSensorMagnetometer。通过将 referenceFrame 选项传递给传感器对象构造函数,用户可以定义是在设备坐标还是屏幕坐标中解析返回的读数。

// Sensor readings are resolved in the Device coordinate system by default.
// Alternatively, could be RelativeOrientationSensor({referenceFrame: "device"}).
const sensorRelDevice = new RelativeOrientationSensor();

// Sensor readings are resolved in the Screen coordinate system. No manual remapping is required!
const sensorRelScreen = new RelativeOrientationSensor({ referenceFrame: 'screen' });

让我们开始编码吧!

Generic Sensor API 非常简单易用!Sensor 接口具有 start()stop() 方法来控制传感器状态,以及多个事件处理脚本来接收有关传感器激活、错误和新可用读数的通知。具体传感器类通常会向基类添加其特定的读数属性。

开发环境

在开发过程中,您可以通过 localhost 使用传感器。如果您要针对移动设备进行开发,请为本地服务器设置端口转发,然后即可大展拳脚了!

代码准备就绪后,将其部署到支持 HTTPS 的服务器上。GitHub 页面采用 HTTPS 协议,是分享演示的绝佳平台。

3D 模型旋转

在这个简单的示例中,我们使用绝对方向传感器的数据来修改 3D 模型的旋转四元数。model 是具有 quaternion 属性的 three.js Object3D 类实例。手机屏幕方向演示中的以下代码段展示了如何使用绝对方向传感器旋转 3D 模型。

function initSensor() {
  sensor = new AbsoluteOrientationSensor({ frequency: 60 });
  sensor.onreading = () => model.quaternion.fromArray(sensor.quaternion);
  sensor.onerror = (event) => {
    if (event.error.name == 'NotReadableError') {
      console.log('Sensor is not available.');
    }
  };
  sensor.start();
}

设备的屏幕方向将反映在 WebGL 场景中的 3D model 旋转中。

传感器更新 3D 模型的方向
传感器更新 3D 模型的方向

Punchmeter

以下代码段摘自冲击力计演示,展示了如何假设设备最初处于静止状态,然后使用线性加速度传感器计算设备的最大速度。

this.maxSpeed = 0;
this.vx = 0;
this.ax = 0;
this.t = 0;

/* … */

this.accel.onreading = () => {
  let dt = (this.accel.timestamp - this.t) * 0.001; // In seconds.
  this.vx += ((this.accel.x + this.ax) / 2) * dt;

  let speed = Math.abs(this.vx);

  if (this.maxSpeed < speed) {
    this.maxSpeed = speed;
  }

  this.t = this.accel.timestamp;
  this.ax = this.accel.x;
};

当前速度是加速度函数积分的近似值。

用于测量冲击速度的演示 Web 应用
测量冲击速度

使用 Chrome DevTools 进行调试和传感器替换

在某些情况下,您不需要实体设备也能使用 Generic 传感器 API。Chrome 开发者工具为模拟设备屏幕方向提供出色的支持。

Chrome DevTools 用于替换虚拟手机的自定义屏幕方向数据
使用 Chrome 开发者工具模拟设备屏幕方向

隐私权和安全

传感器读数属于敏感数据,可能会受到恶意网页的各种攻击。通用传感器 API 的实现会强制执行一些限制,以降低可能的安全和隐私风险。打算使用该 API 的开发者必须考虑这些限制,下面简要列出了这些限制。

仅限 HTTPS

由于通用传感器 API 是一项强大的功能,因此浏览器仅允许在安全情境中使用该 API。实际上,这意味着若要使用 Generic Sensor API,您需要通过 HTTPS 访问网页。在开发期间,您可以通过 http://localhost 进行测试,但对于生产环境,您需要在服务器上启用 HTTPS。如需了解最佳实践和指南,请参阅安全集合。

权限政策集成

通用传感器 API 中的权限政策集成可控制对帧传感器数据的访问权限。

默认情况下,只能在主框架或同源子框架中创建 Sensor 对象,从而防止跨源 iframe 未经授权读取传感器数据。您可以通过显式启用或停用相应的受政策控制的功能来修改此默认行为。

以下代码段展示了向跨源 iframe 授予加速度计数据访问权限,这意味着现在可以在其中创建 AccelerometerLinearAccelerationSensor 对象。

<iframe src="https://third-party.com" allow="accelerometer" />

传感器读数传送可能会中止

只有可见的网页(即用户实际与其互动时)才能访问传感器读数。此外,如果用户焦点变为跨源子帧,则不会向父帧提供传感器数据。这可以防止父框架推断用户输入。

后续操作

我们即将实现一组已指定的传感器类,例如环境光传感器距离传感器;不过,得益于通用传感器框架的强大可扩展性,我们预计还会出现更多代表各种传感器类型的新类。

未来工作中的另一个重要领域是改进通用传感器 API 本身。通用传感器规范目前是候选推荐规范,这意味着我们仍有时间进行修复并提供开发者需要的新功能。

您可以提供帮助!

传感器规范已达到候选推荐成熟度级别,因此非常欢迎 Web 和浏览器开发者提供反馈。请告诉我们您希望添加哪些功能,或者您希望修改当前 API 中的哪些内容。

如有需要,请随时针对 Chrome 实现提交规范问题bug

资源

致谢

本文由 Joe MedleyKayce Basques 审核。