開發歷程:讓網頁讀取手機的「體感」——感測器儀表板

這是 LocalPapa Notes 開發紀錄系列的第六篇。前面幾篇談檔案、談架構、談變現;這一篇回到工具本身,而且是 LocalPapa 最能展現「網站能做、AI 對話做不到」的一個——手機感測器儀表板

為什麼這個工具特別

你可以問 AI 各種問題,但你沒辦法叫一段聊天對話去讀你手機現在的傾斜角度、面對的方位、或剛剛搖了多大力。那需要直接存取裝置硬體。

而瀏覽器可以。透過 DeviceMotionDeviceOrientation 兩組原生 API,一個網頁就能讀到加速度計、陀螺儀的即時數據。我們用它做了七個即時面板:水平儀、3D 裝置姿態、指南針、G 力計、震動偵測、原始數值與 CSV 錄製。

而且——一如 LocalPapa 的慣例——這些資料一個位元組都不離開你的手機

最難的不是物理,是「拿到權限」

感測器的數學其實不複雜,真正的麻煩在 iOS 的授權流程

從 iOS 13 起,基於隱私,網頁不能默默讀取動作感測器——必須在使用者的手勢(點擊)當下呼叫 DeviceMotionEvent.requestPermission() 取得授權。不能在頁面載入時自動請求,否則直接被拒。

所以介面設計上,我們刻意做了一個「啟動」按鈕橫幅,而不是一進頁面就要資料:

  • 偵測到 requestPermission 是函式 → 判定為 iOS,顯示啟動按鈕,等使用者點擊那一刻才請求授權、掛上監聽。
  • Android/桌機則直接嘗試監聽;若 1.5 秒內收不到任何 devicemotion 事件,就判定不支援,顯示提示——避免使用者對著一個永遠不會動的空白頁乾等。
這是一個典型的「平台差異吃掉大部分工時」的例子:核心功能一天寫得完,把三種平台的授權與退路處理好才是真正的工作。

把原始數據變成看得懂的儀表

拿到 betagammaalpha 與加速度三軸之後,每個面板都是一道小小的轉換:

水平儀(氣泡):把 beta(前後傾斜 −90~90)映射到氣泡的 Y 軸偏移、gamma(左右傾斜)映射到 X 軸,限制在圓形容器半徑內。當氣泡離中心夠近(< 0.08 倍半徑)就亮起綠色「水平」。

3D 裝置姿態:直接把 rotateX(-beta) rotateY(gamma) 這個 CSS 3D transform 套到一個手機模型 div 上,畫面就即時跟著你手上的手機轉。

指南針:用 DeviceOrientation.alpha(指北方位角 0~360),對 SVG 指針做 rotate(-alpha),讓指針永遠指向北。

G 力計:用含重力的加速度三軸算向量長度 √(x²+y²+z²),除以 9.81 換成 G 值——靜止約 1G,甩動時會破 2G。

震動偵測:比較相鄰兩次事件的重力向量長度差(delta),超過 18 m/s²、且距上次偵測超過 600ms,就計為一次震動。兩個門檻(強度與時間間隔)一起用,才不會把連續抖動算成好幾十次。

一個刻意的技術選擇:不依賴任何外部函式庫

整個儀表板用純 CSS + SVG 實作,除了共用的 tools-common.js 之外,沒有任何外部 JS 依賴。

為什麼?因為感測器工具最常在手機、甚至訊號不好的場合使用。沒有外部相依,就代表首次載入快、且能離線運作——你在地下室、在山上,它照樣動。這跟前面 PDF 工具刻意用 CDN 懶載入是不同情境下的同一種考量:永遠為「使用當下的真實環境」而設計。

後來它還會「串流」

這個儀表板後來持續長大:加上了把感測器數據即時串流到電腦的能力——透過 WebSocket、HTTP POST,甚至 Web Serial(USB)有線傳輸,定頻(10/30/60Hz)把 JSON 封包送到你自己的接收端,還附上一鍵複製的 Python 接收腳本。後續又補上 GPS 與磁力計。但那是另一段故事,之後再聊。

學到的事

  • 差異化來自「只有這個平台能做的事」。 瀏覽器能直接碰硬體,這是它對 AI 對話的結構性優勢;把它做好,就是 LocalPapa 的護城河之一。
  • 平台授權差異是隱形的大工程。 iOS 必須在手勢內請求、Android 用 timeout 偵測不支援——這些「邊角」才是體驗好壞的分水嶺。
  • 零依賴是一種離線承諾。 越是現場、行動場景的工具,越該砍掉外部相依。
  • 本地處理在感測器上更顯重要。 你的動作、方位、位置是高度個人的資料,理所當然該留在你自己的裝置裡。

一以貫之的問題

這個工具把系列的核心問題推到一個新的邊界:不只是「能不能在瀏覽器裡完成」,而是「有沒有什麼,正好只有瀏覽器能做」?

讀取你手機此刻的體感——答案是:可以,而且這正是網站勝過聊天框的地方。

下一篇開發紀錄,我們會走進那段「串流」的故事:怎麼把手機變成一個即時感測器資料源。想追蹤系列?把 LocalPapa Notes 加入書籤吧。

Dev Log: Reading Your Phone's Senses in a Web Page — the Sensor Dashboard

This is the sixth post in the LocalPapa Notes dev-log series. The last few were about files, architecture and monetisation; this one returns to a tool itself — and one that best shows off "what a website can do that an AI chat can't": the Phone Sensor Dashboard.

Why this tool is special

You can ask an AI all sorts of questions, but you can't tell a chat conversation to read your phone's current tilt angle, the direction it's facing, or how hard you just shook it. That requires direct access to device hardware.

A browser can. Through the native DeviceMotion and DeviceOrientation APIs, a single web page can read live data from the accelerometer and gyroscope. We used them to build seven live panels: a level, a 3D device pose, a compass, a G-meter, shake detection, raw readouts and CSV recording.

And — as is the LocalPapa way — not one byte of this data leaves your phone.

The hard part isn't the physics — it's getting permission

The sensor maths isn't actually complicated. The real headache is the iOS permission flow.

Since iOS 13, for privacy reasons a web page can't silently read motion sensors — it must call DeviceMotionEvent.requestPermission() within a user gesture (a tap) to get authorisation. You can't request automatically on page load, or you're rejected outright.

So in the UI we deliberately built a "Start" button banner rather than demanding data the moment you arrive:

  • Detect that requestPermission is a function → it's iOS, show the start button, and only request permission and attach listeners at the moment the user taps.
  • On Android / desktop we just try listening; if no devicemotion event arrives within 1.5 seconds, we mark it unsupported and show a hint — so users don't stare at a blank page that will never move.
This is a classic case of "platform differences eat most of the hours": the core feature is a day's work; handling three platforms' permissions and fallbacks is the real job.

Turning raw data into readable gauges

Once you have beta / gamma / alpha and the three acceleration axes, each panel is a small conversion:

Level (bubble): map beta (front/back tilt, −90~90) to the bubble's Y offset and gamma (left/right tilt) to X, clamped within the circular container's radius. When the bubble is close enough to centre (< 0.08× radius), a green "level" lights up.

3D device pose: apply the CSS 3D transform rotateX(-beta) rotateY(gamma) directly to a phone-model div, so the on-screen model turns in real time with the phone in your hand.

Compass: use DeviceOrientation.alpha (north-referenced heading, 0~360) to rotate(-alpha) an SVG needle so it always points north.

G-meter: from the gravity-inclusive acceleration axes, compute the vector length √(x²+y²+z²) and divide by 9.81 to get G — about 1G at rest, over 2G when you swing it.

Shake detection: compare the gravity-vector length difference (delta) between consecutive events; if it exceeds 18 m/s² and more than 600ms has passed since the last detection, count one shake. Using both thresholds (magnitude and time gap) together avoids counting one continuous wobble as dozens.

A deliberate technical choice: no external libraries

The whole dashboard is built with pure CSS + SVG, with no external JS dependency beyond the shared tools-common.js.

Why? Because a sensor tool is most often used on a phone, sometimes with poor signal. No external dependencies means fast first load and offline operation — in a basement, up a mountain, it still works. This is the same consideration as the PDF tools' deliberate CDN lazy-loading, in a different context: always design for the real environment of use.

Later, it learned to "stream"

This dashboard kept growing: it gained the ability to stream sensor data live to a computer — over WebSocket, HTTP POST, even Web Serial (USB) wired transfer — sending JSON packets at a fixed rate (10/30/60Hz) to your own receiver, complete with a one-click-copy Python receiving script. GPS and a magnetometer came later too. But that's another story for another day.

What we learned

  • Differentiation comes from "what only this platform can do." A browser can touch hardware directly — a structural advantage over AI chat. Doing it well is one of LocalPapa's moats.
  • Platform permission differences are an invisible big project. iOS must request within a gesture; Android uses a timeout to detect non-support — these "edges" are what separate a good experience from a bad one.
  • Zero dependencies is an offline promise. The more field- and mobile-oriented a tool, the more you should cut external dependencies.
  • Local processing matters even more for sensors. Your motion, orientation and location are deeply personal data — they belong on your own device.

The same question, pushed further

This tool pushes the series' core question to a new edge: not just "can this be done in the browser," but "is there something only the browser can do?"

Reading your phone's senses, right now — the answer is yes, and that's exactly where a website beats a chat box.

In the next dev log, we step into that "streaming" story: how to turn a phone into a live sensor data source. Want to follow the series? Bookmark LocalPapa Notes.

探索 39+ 個隱私優先的瀏覽器工具
全程本地運算、檔案不上傳。
前往 LocalPapa →
Explore 39+ privacy-first browser tools
Everything runs locally — your files never leave your device.
Visit LocalPapa →

想看英文版?點右上角 EN 切換語言。

Prefer Chinese? Tap at the top-right to switch.