Web 传感器

使用通用传感器 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?

与现有接口相比,Generic Sensor API 具有诸多优势:

  • Generic Sensor API 是一种传感器框架,可通过新的传感器类轻松扩展,并且每个类都将保留通用接口。为一种传感器类型编写的客户端代码只需进行少量修改,即可用于另一种传感器类型!
  • 您可以配置传感器。例如,您可以设置适合应用需求的采样频率。
  • 您可以检测平台上是否有传感器。
  • 传感器读数具有高精度时间戳,可更好地与应用中的其他活动同步。
  • 传感器数据模型和坐标系已明确定义,让浏览器供应商能够实现可互操作的解决方案。
  • 基于通用传感器的接口未绑定到 DOM(这意味着它们既不是 navigator 也不是 window 对象),这为未来在服务工作器中使用该 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

对于不支持 Generic Sensor 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 陀螺仪容易出现漂移,这是由传感器的重力感度引起的,重力感度会使传感器的内部机械系统变形。陀螺仪以相对较高的频率振荡,例如几十 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 模型的方向

冲压计

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

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 进行调试和传感器替换

在某些情况下,您无需实体设备即可使用通用传感器 API。Chrome 开发者工具非常支持模拟设备屏幕方向

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

隐私权和安全

传感器读数属于敏感数据,可能会受到恶意网页的各种攻击。通用传感器 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 审核。