使用 Generic Sensor API 存取裝置上的感應器,例如加速計、陀螺儀和磁力計。
如今,許多平台專屬應用程式都會使用感應器資料,以支援沉浸式遊戲、健身追蹤、擴增實境或虛擬實境等用途。如果能縮小平台專屬應用程式和網頁應用程式之間的差距,不是很棒嗎?輸入網頁適用的通用感應器 API!
什麼是 Generic Sensor API?
一般感應器 API 是一組介面,可將感應器裝置公開給網路平台。這個 API 包含基礎 Sensor 介面,以及一組建構在頂端的具體感應器類別。有了基本介面,具體感應器類別的實作和規格程序就會簡化。舉例來說,請參閱 Gyroscope 類別。這個裝置非常小,核心功能是由基本介面指定,而 Gyroscope 只是以代表角速度的三個屬性擴充該介面。
部分感應器類別會與實際的硬體感應器介接,例如加速計或陀螺儀類別。這些稱為低層級感應器。其他感應器 (稱為「融合感應器」) 會合併多個低階感應器的資料,以顯示指令碼原本需要計算的資訊。舉例來說,AbsoluteOrientation 感應器會根據加速計、陀螺儀和磁力計取得的資料,提供可直接使用的 4x4 旋轉矩陣。
您可能會認為網頁平台已提供感應器資料,這完全正確!舉例來說,DeviceMotion 和 DeviceOrientation 事件會公開動作感應器資料。那麼,為什麼我們需要新的 API 呢?
相較於現有介面,Generic Sensor API 具有許多優點:
- Generic Sensor API 是一種感應器架構,可輕鬆擴充新的感應器類別,且每個類別都會保留一般介面。為某種感應器類型編寫的用戶端程式碼,只要稍做修改,就能重複用於其他感應器類型!
- 你可以設定感應器。舉例來說,您可以根據應用程式需求設定合適的取樣頻率。
- 你可以偵測平台是否提供感應器。
- 感應器讀取值具有高精確度時間戳記,可與應用程式中的其他活動更妥善地同步。
- 感應器資料模型和座標系統定義明確,方便瀏覽器供應商實作可互通的解決方案。
- 以 Generic Sensor 為基礎的介面不會繫結至 DOM (也就是說,這些介面既不是
navigator物件,也不是window物件),這為日後在服務工作站中使用 API 或在無頭 JavaScript 執行階段 (例如嵌入式裝置) 中實作 API 開啟了機會。 - 安全性和隱私權是 Generic Sensor 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 軸上的加速度會是正值;如果從右向左推動裝置,加速度則會是負值。
加速計可用於步數計算、動作感應或簡單的裝置方向等用途。加速計測量結果通常會與其他來源的資料合併,以建立融合感應器,例如方向感應器。
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 中實作。這種做法效率不彰,也會大幅增加網頁應用程式程式碼的複雜度;網頁應用程式必須監控螢幕方向變化,並對感應器讀數執行座標轉換,這對歐拉角或四元數來說並非易事。
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 非常簡單易用!感應器介面具有 start() 和 stop() 方法,可控制感應器狀態,以及多個事件處理常式,用於接收感應器啟動、錯誤和新讀數的通知。具體感應器類別通常會將特定讀取屬性新增至基礎類別。
開發環境
開發期間,您可以使用 localhost 存取感應器。如果您要開發行動裝置專用的應用程式,請為本機伺服器設定通訊埠轉送,即可開始開發!
程式碼準備就緒後,請部署到支援 HTTPS 的伺服器。GitHub Pages 是透過 HTTPS 提供,因此是分享示範的最佳平台。
3D 模型旋轉
在這個簡單的範例中,我們會使用絕對方向感應器的資料,修改 3D 模型的旋轉四元數。model 是 three.js Object3D 類別例項,具有 quaternion 屬性。下列程式碼片段來自螢幕方向手機示範,說明如何使用絕對方向感應器旋轉 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
由於 Generic Sensor API 是強大的功能,瀏覽器只允許在安全環境中使用。在實務上,這表示如要使用 Generic Sensor API,您必須透過 HTTPS 存取網頁。開發期間,您可以透過 http://localhost 進行,但實際執行時,伺服器必須使用 HTTPS。如需最佳做法和指南,請參閱「安全無虞」系列文章。
整合權限政策
Generic Sensor API 中的權限政策整合可控管影格的感應器資料存取權。
根據預設,Sensor 物件只能在主要影格或同源子影格中建立,因此可防止跨來源 iframe 未經授權讀取感應器資料。如要修改這項預設行為,請明確啟用或停用相應的政策控管功能。
以下程式碼片段說明如何將加速度計資料存取權授予跨來源 iframe,也就是現在可以在該處建立 Accelerometer 或 LinearAccelerationSensor 物件。
<iframe src="https://third-party.com" allow="accelerometer" />
感應器讀數傳送作業可能會暫停
只有在使用者實際與網頁互動時,可見的網頁才能存取感應器讀數。此外,如果使用者焦點變更為跨來源子影格,系統就不會將感應器資料提供給父項影格。這可防止父框架推斷使用者輸入內容。
後續步驟
近期將實作一組已指定的感應器類別,例如環境光度感應器或鄰近感應器;不過,由於通用感應器架構具有極佳的擴充性,我們預期會出現更多代表各種感應器類型的新類別。
未來工作另一個重要領域是改良 Generic Sensor API 本身,Generic Sensor 規格目前是候選建議,這表示我們仍有時間進行修正,並提供開發人員需要的新功能。
你也能盡一份心力!
感應器規格已達到「候選建議」成熟度等級,因此非常感謝網路和瀏覽器開發人員提供意見。歡迎告訴我們您希望新增哪些功能,或目前 API 有哪些地方需要修改。
資源
- 示範專案: https://w3c.github.io/generic-sensor-demos/
- 通用感應器 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
- 實作錯誤: http://crbug.com?q=component:Blink>Sensor
特別銘謝
本文由 Joe Medley 和 Kayce Basques 審查。