網頁感應器

使用 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 高出許多。已整合 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 非常簡單易用!感應器介面提供 start()stop() 方法,可控制感應器狀態,以及用於接收感應器啟動、錯誤和新讀取通知通知的多個事件處理常式。具體的感應器類別通常會將其特定的讀取屬性新增至基礎類別。

開發環境

在開發期間,您可以透過 localhost 使用感應器。如果您是為行動裝置開發應用程式,請為本機伺服器設定通訊埠轉送功能,即可大展身手!

程式碼準備就緒後,請將其部署到支援 HTTPS 的伺服器上。GitHub Pages 是透過 HTTPS 提供,因此非常適合用來分享示範。

3D 模型旋轉

在這個簡單的範例中,我們會使用絕對方向感應器的資料,修改 3D 模型的旋轉四元數。model 是包含 quaternion 屬性的 3.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;
};

目前速率的計算方式是估算加速度函式的完整性。

用於測量 Punch 速度的網頁應用程式範例
測量出拳速度

使用 Chrome 開發人員工具進行偵錯和感應器覆寫

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

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

隱私權與安全性

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

僅限 HTTPS

由於 Generic Sensor 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 審查。