使用通用传感器 API 访问设备上的传感器,例如加速度计、陀螺仪和磁力计。
如今,许多特定于平台的应用都会使用传感器数据来实现沉浸式游戏、健身跟踪以及增强现实或虚拟现实等使用情形。如果能弥合平台专属应用与 Web 应用之间的差距,岂不是很棒?输入适用于 Web 的通用传感器 API!
什么是 Generic Sensor API?
通用传感器 API 是一组接口,可将传感器设备公开给 Web 平台。该 API 由基本 Sensor 接口和一组基于该接口构建的具体传感器类组成。拥有基本接口可简化具体传感器类的实现和规范流程。例如,请查看 Gyroscope 类。它非常小!核心功能由基本接口指定,而 Gyroscope 仅通过表示角速度的三个属性对其进行扩展。
某些传感器类会与实际的硬件传感器(例如加速度计或陀螺仪类)进行交互。这些传感器称为低级传感器。其他传感器(称为融合传感器)会合并来自多个低级别传感器的数据,以公开脚本原本需要计算的信息。例如,AbsoluteOrientation 传感器可根据从加速度计、陀螺仪和磁力计获取的数据提供可直接使用的 4x4 旋转矩阵。
您可能会认为 Web 平台已经提供传感器数据,您说得完全正确!例如,DeviceMotion 和 DeviceOrientation 事件会公开运动传感器数据。那么,为什么我们需要新的 API?
与现有接口相比,Generic Sensor API 具有许多优势:
- 通用传感器 API 是一种传感器框架,可轻松扩展为新的传感器类,并且每个类都将保留通用接口。为一种传感器类型编写的客户端代码只需进行极少的修改,即可重复用于另一种传感器类型!
- 您可以配置传感器。例如,您可以设置适合应用需求的抽样频率。
- 您可以检测平台上的传感器是否可用。
- 传感器读数具有高精度时间戳,可更好地与应用中的其他活动同步。
- 传感器数据模型和坐标系已明确定义,因此浏览器供应商可以实现可互操作的解决方案。
- 基于通用传感器的接口不绑定到 DOM(意味着它们既不是
navigator对象,也不是window对象),这为在服务工作线程中使用该 API 或在无头 JavaScript 运行时(例如嵌入式设备)中实现该 API 开辟了未来的机会。 - 安全性和隐私方面是通用传感器 API 的首要考虑因素,与旧版传感器 API 相比,它可提供更好的安全性。与 Permissions API 集成。
Accelerometer、Gyroscope、LinearAccelerationSensor、AbsoluteOrientationSensor、RelativeOrientationSensor和Magnetometer支持自动与屏幕坐标同步。
可用的通用传感器 API
在撰写本文时,您可以尝试使用多种传感器。
运动传感器:
AccelerometerGyroscopeLinearAccelerationSensorAbsoluteOrientationSensorRelativeOrientationSensorGravitySensor
环境传感器:
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 轴上的加速度将为正;如果您将设备从右向左加速,X 轴上的加速度将为负。
加速度计可用于以下用途:计步、运动感应或简单的设备方向感应。通常,加速度计测量结果会与其他来源的数据相结合,以创建融合传感器,例如方向传感器。
LinearAccelerationSensor 测量施加到托管传感器的设备上的加速度,不包括重力的贡献。当设备处于静止状态(例如平放在桌子上)时,传感器会在三个轴上测量到 ≈ 0 m/s2 的加速度。
重力传感器
用户目前已经可以通过手动检查 Accelerometer 和 LinearAccelerometer 读数来手动推导出接近重力传感器读数的读数,但这可能很麻烦,并且取决于这些传感器提供的值的准确性。Android 等平台可以将重力读数作为操作系统的一部分提供,这样在计算方面应该更经济实惠,根据用户的硬件提供更准确的值,并且在 API 人体工程学方面更易于使用。GravitySensor 返回设备沿 X、Y 和 Z 轴方向因重力而产生的加速度效应。
陀螺仪
Gyroscope 传感器用于测量设备本地 X 轴、Y 轴和 Z 轴周围的角速度(以弧度每秒为单位)。大多数消费类设备都配备了机械(MEMS)陀螺仪,这是一种基于惯性科里奥利力测量旋转速率的惯性传感器。MEMS 陀螺仪容易出现漂移,这是由传感器的重力敏感性引起的,这种敏感性会使传感器的内部机械系统变形。陀螺仪以相对较高的频率振荡,例如因此,与其他传感器相比,它可能会消耗更多电量。
方向传感器
AbsoluteOrientationSensor 是一种融合传感器,用于测量设备相对于地球坐标系的旋转,而 RelativeOrientationSensor 提供的数据表示搭载运动传感器的设备相对于固定参考坐标系的旋转。
所有现代 3D JavaScript 框架都支持使用四元数和旋转矩阵来表示旋转;不过,如果您直接使用 WebGL,OrientationSensor 会同时提供 quaternion 属性和 populateMatrix() 方法。以下是一些代码段:
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();
// 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 提供了一种更简单、更可靠的解决方案!所有已定义的空间传感器类(Accelerometer、Gyroscope、LinearAccelerationSensor、AbsoluteOrientationSensor、RelativeOrientationSensor 和 Magnetometer)的本地坐标系均可配置。通过将 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 Pages 采用 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 旋转中。
冲力计
以下代码段摘自 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;
};
当前速度是根据加速度函数的积分近似计算得出的。
使用 Chrome 开发者工具进行调试和传感器替换
在某些情况下,您无需使用实体设备即可体验 Generic Sensor API。Chrome 开发者工具非常支持模拟设备方向。
隐私权和安全
传感器读数是敏感数据,可能会受到恶意网页的各种攻击。通用传感器 API 的实现会强制执行一些限制,以降低可能存在的安全和隐私风险。打算使用该 API 的开发者必须考虑这些限制,因此我们在此简要列出这些限制。
仅限 HTTPS
由于通用传感器 API 是一项强大的功能,因此浏览器仅允许在安全情境中使用它。在实践中,这意味着要使用 Generic Sensor API,您需要通过 HTTPS 访问网页。在开发期间,您可以通过 http://localhost 执行此操作,但在生产环境中,您需要在服务器上启用 HTTPS。如需了解最佳实践和指南,请参阅安全无忧合集。
“权限”政策集成
通用传感器 API 中的权限政策集成可控制对帧的传感器数据的访问权限。
默认情况下,Sensor 对象只能在主框架或同源子框架内创建,从而防止跨源 iframe 未经授权读取传感器数据。您可以通过显式启用或停用相应的受政策控制的功能来修改此默认行为。
以下代码段展示了如何向跨源 iframe 授予加速度计数据访问权限,这意味着现在可以在其中创建 Accelerometer 或 LinearAccelerationSensor 对象。
<iframe src="https://third-party.com" allow="accelerometer" />
可以暂停传感器读数交付
传感器读数只能由可见的网页访问,即当用户实际与网页互动时。此外,如果用户焦点更改为跨源子框架,则不会向父框架提供传感器数据。这可防止父框架推断用户输入。
后续操作
有一组已指定的传感器类将在不久的将来实现,例如环境光传感器或距离传感器;不过,得益于通用传感器框架的出色可扩展性,我们可以预见,将出现更多表示各种传感器类型的新类。
未来工作的另一个重要领域是改进通用传感器 API 本身。通用传感器规范目前是候选推荐规范,这意味着我们仍有时间进行修复并引入开发者需要的新功能。
您可以尽一份力!
传感器规范已达到候选建议成熟度级别,因此,我们非常欢迎 Web 和浏览器开发者的反馈。请告诉我们您希望添加哪些功能,或者您希望修改当前 API 中的哪些内容。
资源
- 演示项目: https://w3c.github.io/generic-sensor-demos/
- Generic Sensor API 规范:https://w3c.github.io/sensors/
- 规范问题: https://github.com/w3c/sensors/issues
- W3C 工作组邮寄名单:public-device-apis@w3.org
- Chrome 功能状态: https://www.chromestatus.com/feature/5698781827825664
- 实现 bug: http://crbug.com?q=component:Blink>Sensor
致谢
本文由 Joe Medley 和 Kayce Basques 审核。