網頁感應器

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

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

如今,許多平台專屬應用程式都會使用感應器資料,以支援沉浸式遊戲、健身追蹤、擴增實境或虛擬實境等用途。如果能縮小平台專屬應用程式和網頁應用程式之間的差距,不是很棒嗎?輸入網頁適用的通用感應器 API

什麼是 Generic Sensor API?

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

部分感應器類別會與實際的硬體感應器介接,例如加速計或陀螺儀類別。這些稱為低層級感應器。其他感應器 (稱為「融合感應器」) 會合併多個低階感應器的資料,以顯示指令碼原本需要計算的資訊。舉例來說,AbsoluteOrientation 感應器會根據加速計、陀螺儀和磁力計取得的資料,提供可直接使用的 4x4 旋轉矩陣。

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

相較於現有介面,Generic Sensor API 具有許多優點:

  • Generic Sensor API 是一種感應器架構,可輕鬆擴充新的感應器類別,且每個類別都會保留一般介面。為某種感應器類型編寫的用戶端程式碼,只要稍做修改,就能重複用於其他感應器類型!
  • 你可以設定感應器。舉例來說,您可以根據應用程式需求設定合適的取樣頻率。
  • 你可以偵測平台是否提供感應器。
  • 感應器讀取值具有高精確度時間戳記,可與應用程式中的其他活動更妥善地同步。
  • 感應器資料模型和座標系統定義明確,方便瀏覽器供應商實作可互通的解決方案。
  • 以 Generic Sensor 為基礎的介面不會繫結至 DOM (也就是說,這些介面既不是 navigator 物件,也不是 window 物件),這為日後在服務工作站中使用 API 或在無頭 JavaScript 執行階段 (例如嵌入式裝置) 中實作 API 開啟了機會。
  • 安全性和隱私權是 Generic Sensor 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 陀螺儀容易因感應器的重力敏感度而發生漂移,導致感應器的內部機械系統變形。陀螺儀會以相對較高的頻率震盪,例如 因此相較於其他感應器,可能耗用更多電力。

方向感應器

絕對方向感應器測量結果

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

開始編寫程式碼!

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 旋轉中。

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

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 開發人員工具可模擬裝置方向

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 本身,Generic Sensor 規格目前是候選建議,這表示我們仍有時間進行修正,並提供開發人員需要的新功能。

你也能盡一份心力!

感應器規格已達到「候選建議」成熟度等級,因此非常感謝網路和瀏覽器開發人員提供意見。歡迎告訴我們您希望新增哪些功能,或目前 API 有哪些地方需要修改。

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

資源

特別銘謝

本文由 Joe MedleyKayce Basques 審查。