網頁感應器

使用 Generic Sensor API 存取裝置上的感應器,例如加速計、陀螺儀和磁力計。

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

如今,感應器資料已廣泛應用於許多平台專屬應用程式,用於沉浸式遊戲、健身追蹤,以及擴增或虛擬實境等用途。將特定平台和網頁應用程式之間的差距縮小,這不是很棒嗎?輸入通用感應器 API,用於網頁!

什麼是 Generic Sensor API?

Generic Sensor API 是一組介面,可將感應器裝置公開給網路平台。這個 API 包含基礎 Sensor 介面,以及一組在其上建構的具體感應器類別。有了基本介面,就能簡化具體感應器類別的實作和規格程序。舉例來說,請查看 Gyroscope 類別。它超級小!核心功能由基礎介面指定,而 Gyroscope 只會使用三個代表角速度的屬性擴充功能。

有些感應器類別會與實際的硬體感應器連結,例如加速計或陀螺儀類別。這些稱為低層級感應器。其他感應器 (稱為融合感應器) 會合併多個低階感應器的資料,以便提供指令碼原本需要計算的資訊。舉例來說,AbsoluteOrientation 感應器會根據從加速計、陀螺儀和磁力計取得的資料,提供可立即使用的四乘四旋轉矩陣。

您可能會認為網頁平台已提供感應器資料,而這點完全正確!舉例來說,DeviceMotionDeviceOrientation 事件會公開動作感應器資料。那麼,為什麼我們需要新的 API?

與現有的介面相比,通用感應器 API 提供許多優點:

  • 通用感應器 API 是一種感應器架構,可輕鬆透過新的感應器類別擴充,而每個類別都會保留通用介面。為一種感應器類型編寫的用戶端程式碼,只要稍加修改,就能重複用於其他感應器類型。
  • 您可以設定感應器。舉例來說,您可以設定適合應用程式需求的取樣頻率。
  • 您可以偵測平台上是否有感應器。
  • 感應器讀數具有高精確度的時間戳記,可與應用程式中的其他活動進行更佳同步。
  • 感應器資料模型和座標系統已明確定義,讓瀏覽器供應商能夠導入可互通的解決方案。
  • 以通用感應器為基礎的介面不會繫結至 DOM (也就是說,它們既不是 navigator 也不是 window 物件),這為日後在服務 worker 中使用 API,或在無頭 JavaScript 執行階段 (例如嵌入式裝置) 中實作 API 開啟了機會。
  • 安全性和隱私權是通用感應器 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;
  }
}

聚酯纖維

對於不支援通用感應器 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 陀螺儀容易出現漂移現象,這是因為感應器的引力靈敏度會使感應器的內部機械系統變形。陀螺儀以相對較高的頻率振動,例如因此,相較於其他感應器,可能會耗用更多電力。

方向感應器

絕對方向感應器測量結果

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 將感應器讀數重新對應至螢幕座標。這種做法效率不彰,而且也會大幅增加網頁應用程式程式碼的複雜度;網頁應用程式必須監控螢幕方向變更,並為感應器讀數執行座標轉換,而這對於歐拉角或四元數來說,並非易事。

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' });

開始編寫程式碼吧!

通用感應器 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 旋轉。

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

Punchmeter

下列程式碼片段是從 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 開發人員工具進行偵錯和感應器覆寫

在某些情況下,您不必使用實體裝置就能操作通用感應器 API。Chrome 開發人員工具可提供模擬裝置方向的絕佳支援。

Chrome 開發人員工具,用於覆寫虛擬手機的自訂方向資料
使用 Chrome 開發人員工具模擬裝置方向

隱私權與安全性

感應器讀數屬於機密資料,可能會遭受惡意網頁的各種攻擊。通用感應器 API 的實作會強制執行一些限制,以降低可能的安全性和隱私權風險。有意使用 API 的開發人員必須考量這些限制,因此讓我們簡單列出這些限制。

僅限 HTTPS

由於通用感應器 API 是一項強大的功能,瀏覽器只會在安全情境中允許使用這項功能。換句話說,如要使用 Generic Sensor API,您必須透過 HTTPS 存取網頁。開發期間可透過 http://localhost 執行,但實際執行時,您需要在伺服器上使用 HTTPS。如需最佳做法和指南,請參閱安全無虞系列。

權限政策整合

通用感應器 API 中的權限政策整合功能,可控管影格感應器資料的存取權。

根據預設,Sensor 物件只能在主框架或同源子框架中建立,因此可防止跨來源 iframe 未經授權就讀取感應器資料。您可以明確啟用或停用對應的政策控制功能,藉此修改這項預設行為。

下列程式碼片段說明如何授予加速計資料存取跨來源 iframe 的權限,也就是說,現在可以在該 iframe 中建立 AccelerometerLinearAccelerationSensor 物件。

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

可暫停傳送感應器讀數

只有可見的網頁 (也就是使用者實際與網頁互動時) 才能存取感應器讀數。此外,如果使用者焦點變更為跨來源子影格,系統就不會將感應器資料提供給父項影格。這可避免父項框架推斷使用者輸入內容。

後續步驟

在近期,我們將實作一組已指定的感應器類別,例如環境光度感應器鄰近感應器;不過,由於通用感應器架構的可擴充性極佳,我們可以預期會有更多代表各種感應器類型的新類別出現。

未來另一個重要的工作領域,是改善通用感應器 API 本身。通用感應器規格目前是候選推薦規格,也就是說,我們仍有時間進行修正,並提供開發人員需要的新功能。

您可以提供協助!

感應器規格已達到候選推薦的成熟度,因此非常歡迎網頁和瀏覽器開發人員提供意見回饋。請告訴我們您希望新增哪些功能,或是您想修改目前 API 中的哪些內容。

歡迎您針對 Chrome 實作提交規格問題錯誤

資源

特別銘謝

本文由 Joe MedleyKayce Basques 審查。