Web 传感器

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

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

如今,传感器数据用于许多平台特有的应用,以实现沉浸式游戏、健身跟踪以及增强现实或虚拟现实等用例。弥合特定于平台的应用与 Web 应用之间的差距不是很酷吗?针对网页版输入 Generic Sensor API

什么是 Generic Sensor API?

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

有些传感器类会连接到实际硬件传感器,如加速度计类或陀螺仪类。这些传感器称为低级别传感器。其他传感器称为“融合传感器”,则会合并来自多个低级别传感器的数据,以公开脚本原本需要计算的信息。例如,AbsoluteOrientation 传感器根据从加速度计、陀螺仪和磁力计获取的数据,提供一个现成的 4x4 旋转矩阵。

您可能会认为网络平台已经提供了传感器数据,您完全正确!例如,DeviceMotionDeviceOrientation 事件会公开移动传感器数据。那么,为什么我们需要新的 API 呢?

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

  • Generic Sensor API 是一个传感器框架,可以使用新的传感器类轻松扩展,其中每个类都将保留通用接口。只需极少的修改,为一种传感器类型编写的客户端代码可重复用于另一种传感器!
  • 您可以配置传感器。例如,您可以设置适合应用需求的采样率。
  • 您可以检测平台上是否有传感器。
  • 传感器读数具有高精确度的时间戳,能够更好地与应用中的其他 activity 同步。
  • 我们明确定义了传感器数据模型和坐标系,以便浏览器供应商实现可互操作的解决方案。
  • 基于通用传感器的接口未绑定到 DOM(这意味着它们既不是 navigator 对象,也不是 window 对象),这为将来在 Service Worker 中使用该 API 或在无头 JavaScript 运行时(例如嵌入式设备)中实现该 API 提供了机会。
  • 安全和隐私是 Generic Sensor 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;
  }
}

聚酯纤维

对于不支持 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 轴)的加速度将等于地球的重力,即设备向上推的力 + 设备向上推 9.8 米/秒时如果您将设备向右推,则 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();

巴比伦

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 选项传递给传感器对象构造函数,用户可以定义返回的读数是在 device 还是屏幕坐标中解析。

// 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 开发者工具进行调试和传感器替换

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

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

隐私权和安全性

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

仅 HTTPS

由于 Generic Sensor API 是一项强大的功能,因此浏览器仅允许在安全上下文中使用。实际上,这意味着要使用 Generic Sensor API,您需要通过 HTTPS 访问您的页面。在开发期间,您可以通过 http://localhost 执行此操作,但对于生产环境,您需要在服务器上使用 HTTPS。如需了解最佳做法和指南,请参阅安全可靠集合。

权限政策集成

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

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

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

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

可能会暂停发送传感器读数

传感器读数只能通过可见网页访问,即在用户实际与网页互动时。此外,如果用户焦点变为跨源子帧,传感器数据将不会提供给父帧。这样可以防止父框架推断用户输入。

后续操作

有一组指定的传感器类将在不久的将来实现,例如环境光传感器近程传感器;不过,由于通用传感器框架具有出色的可扩展性,我们预计还可以看到更多代表各种传感器类型的新类。

未来工作的另一个重要领域是改进 Generic Sensor API 本身,通用传感器规范目前是候选建议,这意味着开发者仍有时间进行修复并引入开发者需要的新功能。

您可以帮忙!

这些传感器规格已达到候选建议成熟度级别,因此非常感谢您的反馈。请告知我们哪些功能值得添加,或者您想要在当前 API 中修改某些内容。

欢迎随时提交 Chrome 实现的规范问题bugs

资源

致谢

本文由 Joe MedleyKayce Basques 审核。主打图片由 Misko 通过 Wikimedia Commons 提供。