文件型網頁的響應式靠重排(reflow):欄位換行、字級縮放、side nav 變 hamburger。遊戲畫面沒有這個選項——一張 1920×1080 的戰場不能「換行」。遊戲的響應式只能靠縮放(scale)+取景(framing):先固定一個設計解析度,再決定螢幕比例不合時要補黑邊、要裁切、還是要放寬視野。這是兩套完全不同的技術,混用就會做出「在 iPhone 上底部按鈕被 home indicator 蓋住、canvas 糊成一片、轉橫向後跑版」的東西。

本專欄只處理一個具體目標:一塊佔滿整個螢幕寬高的遊戲畫面,在手機、平板、桌機上都正確、清晰、不跑版、不掉幀。每篇原子筆記給可貼上就跑的 CSS/JS,並標明哪些是穩定標準、哪些是平台相依的坑。

三個必須先接受的事實

一、100vh 在行動瀏覽器是錯的,而且錯得不一致。 vh 綁的是大視口(large viewport),也就是工具列收起時的高度。工具列展開時 100vh 比實際可視區大,底部內容被吃掉;而工具列的展開/收合又由捲動手勢驅動,所以同一頁在同一支手機上會有兩種高度。正解是 CSS Values 4 的 svhlvhdvh 三組單位,2025 年 6 月起已是 Baseline Widely available。

二、CSS 像素不是螢幕像素,canvas 必須自己補上 DPR。 <canvas> 有兩套尺寸:style.width/height(版面佔多大)與 canvas.width/height(backing store 有幾個像素)。只設前者,瀏覽器會把低解析度的 bitmap 放大,在 DPR 3 的手機上就是三倍模糊。必須 canvas.width = cssWidth × devicePixelRatio,再用 ctx.scale(dpr, dpr) 把座標系換回 CSS 像素。

三、瀏海與 home indicator 預設會蓋住你的內容,除非你主動要求。 加了 viewport-fit=cover 才能畫到螢幕邊緣,但同時 env(safe-area-inset-*) 才會變成非零值——你必須自己把 UI 推進安全區。不加 cover 則永遠拿不到滿版;加了不處理 inset 則按鈕被切掉。兩者是綁在一起的一組決策。

能力階段

階段你已經能做到對應筆記
觀念正確說得出重排與縮放的差別、知道遊戲該用哪一套01
視口設定對meta viewport 寫對、知道 svh/lvh/dvh 各該用在哪02
容器真的滿版沒有捲軸、沒有橡皮筋、safe-area 有處理03
會縮放畫布能實作 letterbox/cover/expand 三種取景並選對04
畫面是清晰的DPR、backing store、render scale 都算對05
尺寸變化不會壞ResizeObserver 重算、轉向不跑版、無 layout thrash06–07
跨裝置直橫向、觸控 vs 鍵鼠、平板桌機的 UI 重排08
沉浸體驗Fullscreen、鎖定方向、防縮放防長按,且有 iOS 降級09
輸入不擋畫面鍵盤彈出用 visualViewport 正確處理10
效能達標依裝置調 render scale、動態解析度、分層渲染11
會排錯看到症狀能直接定位原因12–13

「我想做到⋯⋯」索引

  • 我只要一塊 16:9 的畫面永遠置中、完整可見、周圍補黑畫布縮放策略 的 letterbox,純 CSS 版只要 aspect-ratiomax-width/max-height + 置中容器。
  • 我要真正滿版、不要黑邊,可以裁掉一點邊緣 → 同篇的 cover 策略,配合「安全框(safe frame)」規劃 UI 位置。
  • 我要像現代手遊那樣:UI 貼齊四角,視野隨螢幕變寬 → 同篇的 expand(彈性視野),這是 16:9 到 20:9 差距這麼大的今天最實用的一種。
  • 我的 canvas 在 iPhone 上很糊DPR 與清晰度:backing store 沒乘 devicePixelRatio,或 canvas.width 設完之後忘了 context 狀態已被重設。
  • 底部按鈕被 Safari 工具列/home indicator 蓋住視口單位svh滿版容器env(safe-area-inset-bottom)
  • 手機上整頁會被拖動、出現橡皮筋回彈滿版容器position: fixed 根容器 + overscroll-behaviortouch-action: none
  • 轉橫向之後版面錯位,要轉兩次才對重算迴圈orientationchangeresize 讀到的是舊尺寸,改用 ResizeObserver
  • 我要全螢幕並鎖橫向全螢幕與沉浸模式,含 iPhone 上做不到時的旋轉 stage 降級方案。
  • 輸入框一點下去鍵盤把整個遊戲頂上去鍵盤與 visualViewport
  • 低階 Android 掉幀render scale 與效能 的動態解析度演算法。
  • 我想直接查症狀常見錯誤與排錯

學習路徑

  1. 響應式的真義:重排 vs 縮放,遊戲屬於哪一邊
  2. 視口設定:meta viewport 與 svh/lvh/dvh 的正確用法
  3. 真正滿版的容器:safe-area、無捲軸、無橡皮筋
  4. 畫布縮放策略:letterbox/cover/expand 的數學與實作
  5. DPR 與清晰度:backing store、render scale、像素藝術
  6. 重算迴圈:ResizeObserver、轉向時序與 layout thrash
  7. CSS transform 縮放 vs 直接 resize:取捨與輸入座標換算
  8. 方向與裝置:直橫向、觸控 vs 鍵鼠、平板與桌機的 UI 重排
  9. 全螢幕與沉浸模式:Fullscreen API、鎖定方向、iOS 的天花板
  10. 鍵盤與 visualViewport:不讓輸入法頂爛版面
  11. 效能:render scale、動態解析度與分層渲染
  12. 常見錯誤與排錯(症狀導向)
  13. 主張 vs 可佐證:哪些是標準、哪些是平台相依

與站內其他筆記的關係

  • Dark Theme 最佳實踐:同一個「行動端視口」問題的另一半。那篇處理顏色與主題(theme-color、安全區域白邊、WebView 強制反色),本專欄處理幾何與尺寸;viewport-fit=cover 造成的安全區白邊在兩邊都會出現,見 mobile web 專篇
  • 前端設計模式:本專欄處理的是「畫面幾何」,那篇處理的是「狀態與架構」。遊戲的 resize/orientation/fullscreen 三態切換,本質上就是那篇講的 State Machine 適用場景。
  • Agentic 產品的串流 UIUX:兩者共用同一個難題——UI 尺寸在執行期會變,版面必須從「一次算好」改成「持續協調」。
  • 可維護技術棧與 SEO/無障礙基本盤:本專欄多處用到 touch-action: noneuser-scalable=no 這類會傷害無障礙的手段,取捨原則見那篇與本專欄 13。

🔍 待解問題 / 持續追蹤

  • Safari 何時原生支援 interactive-widget?在它落地前,iOS 上鍵盤彈出只能靠 visualViewport 手動補償,而這條路在捲動中的中間狀態仍有抖動。
  • iPhone 上任意元素的 Fullscreen API 狀態反覆(caniuse 長期標為 partial、部分版本需開旗標),是否已可在 2026 年的量產程式碼中依賴?目前結論仍是「必須 feature-detect + 準備降級」。
  • Safari 的 canvas 記憶體上限(單一 canvas 面積與行程總量)沒有公開文件,只有社群實測值。多層 canvas + 高 DPR 的組合在 iOS 上何時會被回收,缺乏可預測的模型。
  • dvh 的更新被瀏覽器節流甚至 debounce(web.dev 明確說明不保證 60fps),用它做遊戲主容器高度在快速捲動時會抖。目前的實務解法是「版面用 svh 固定、視覺滿版靠 position: fixed」,但這是繞道而非正解。
  • 折疊機(env(viewport-segment-*))上的滿版遊戲該怎麼取景?跨越鉸鏈的裁切區目前沒有業界共識。

🕒 更新紀錄

  • 2026-08-14:初版,建立 hub + 13 篇原子筆記(視口單位、滿版容器、畫布縮放三策略、DPR、重算迴圈、transform vs resize、方向與裝置、全螢幕、鍵盤、效能、排錯、證據檢視)。

此資料夾下有 13 條筆記。