用螢幕寬度推測輸入方式是最普遍也最錯誤的假設。1366px 寬可能是筆電(滑鼠)、可能是觸控筆電(手指 + 滑鼠)、可能是橫放的平板(手指)、也可能是接了鍵盤的平板(手指 + 鍵盤)。Media Queries Level 4 提供的 pointerhover 直接查詢輸入能力,這才是正確的判斷依據。

查詢輸入能力

查詢意義
pointernone / coarse / fine主要指標裝置的精度。coarse = 手指,fine = 滑鼠/觸控筆
any-pointer同上任一可用指標裝置。觸控筆電的 any-pointer 同時滿足 coarsefine
hovernone / hover主要指標能否停留產生 hover 狀態
any-hover同上任一指標能否 hover
/* 滑鼠:可以用小控制項、可以做 hover 提示 */
@media (hover: hover) and (pointer: fine) {
  .btn { min-height: 28px; }
  .btn:hover { background: #2a2a32; }
  .tooltip-on-hover { display: block; }
}
 
/* 手指:命中區必須夠大,hover 提示要改成長按或常駐 */
@media (pointer: coarse) {
  .btn { min-height: 48px; min-width: 48px; }   /* Material 48dp */
  .tooltip-on-hover { display: none; }
}
 
/* 混合裝置:兩種都要能用 → 以大命中區為準,但保留 hover 效果 */
@media (any-pointer: coarse) and (any-hover: hover) {
  .btn { min-height: 44px; }
}

命中區的兩個業界基準:Apple Human Interface Guidelines 建議 44×44pt,Material Design 建議 48×48dp。取 44px 是安全的下限,遊戲中的關鍵按鈕(攻擊、跳躍)建議再放大到 56–64px。

重點是命中區不隨遊戲 scale 縮小。畫布縮到 0.4 倍時,畫在 canvas 內的 48px 按鈕實際只剩 19px。解法有兩種:把互動控制項放在 DOM 層(不受 canvas transform 影響),或在 canvas 內另外維持一個以 CSS 像素計的觸控半徑:

// canvas 內的觸控判定:把門檻換算回設計座標
const TOUCH_RADIUS_CSS = 24                       // 想要的實際半徑(CSS 像素)
const radiusInDesign = TOUCH_RADIUS_CSS / view.scale
const hit = dist(pointerDesign, button.center) < Math.max(button.r, radiusInDesign)

方向:三種處理策略

(orientation: portrait) 比對的是視口長寬(height >= width),不是裝置的物理姿勢。這正是你要的語意。

策略 A:兩種方向都支援,UI 重排。 最好但成本最高。橫向把控制項移到左右兩側(拇指自然位置),直向移到底部。遊戲畫布的取景策略要能吃下兩種比例——expand 模式在這裡最順,因為它本來就是「安全框固定、視野彈性」。

@media (orientation: landscape) {
  .controls { flex-direction: row; justify-content: space-between; align-items: flex-end; }
}
@media (orientation: portrait) {
  .controls { flex-direction: column; align-items: center; }
}

策略 B:只支援一種方向,另一種顯示提示。 最省事,體驗可接受。要用 CSS 而不是 JS,因為 CSS 沒有時序問題:

.rotate-hint { display: none; }
@media (orientation: portrait) and (pointer: coarse) {
  .game-root { display: none; }
  .rotate-hint { display: grid; place-items: center; }
}

注意加上 (pointer: coarse):桌機使用者把視窗拉窄成直的,不該看到「請旋轉裝置」。

策略 C:軟性旋轉。 方向錯誤時,用 CSS 把整個 stage 旋轉 90 度:

@media (orientation: portrait) {
  .stage {
    width: 100vh; height: 100vw;
    transform: translate(-50%, -50%) rotate(90deg);
    position: absolute; top: 50%; left: 50%;
    transform-origin: center center;
  }
}

這是 iPhone 上唯一能達成「強制橫向」效果的方法(見 全螢幕與沉浸模式),但代價很實在:輸入座標要跟著旋轉getBoundingClientRect() 對旋轉元素回傳的是外接矩形,換算會錯)、所有 DOM 疊層都被一起轉、文字輸入與系統 UI 方向不一致。只在真的沒有其他選擇時用。

若採用策略 C,座標換算不能再用外接矩形,要改用完整的反矩陣:

const m = new DOMMatrixReadOnly(getComputedStyle(stage).transform)
const inv = m.inverse()
const p = inv.transformPoint(new DOMPoint(ev.clientX - originX, ev.clientY - originY))

平板與桌機:畫面變大時該做什麼

畫面變大不代表要把所有東西等比放大——那只會得到「巨大的手機介面」。三個可行方向:

一、視野變大(expand)。 桌機看到更多戰場,這是最自然的用法。要注意公平性與鏡頭邊界(見 畫布縮放策略)。

二、UI 密度提高、資訊上浮。 手機上要點兩層選單才看得到的資訊(背包、技能樹、聊天),桌機直接常駐側欄。這是純粹的重排問題,用 media query 處理,與畫布縮放無關:

.layout { display: grid; grid-template-areas: "stage"; }
 
@media (min-width: 1024px) and (pointer: fine) {
  .layout {
    grid-template-columns: 1fr 320px;
    grid-template-areas: "stage sidebar";
  }
  .sidebar { display: block; }   /* 手機上是 display: none 的抽屜 */
}

三、控制項改變形態。 手機的虛擬搖桿在桌機應該消失,換成 WASD + 滑鼠。這要由 pointer 查詢與實際收到的事件共同決定——最穩的做法是兩套都準備好,依最後一次輸入事件的 pointerType 動態切換

let lastInput = matchMedia('(pointer: coarse)').matches ? 'touch' : 'mouse'
addEventListener('pointerdown', e => setInputMode(e.pointerType === 'touch' ? 'touch' : 'mouse'))
addEventListener('keydown', () => setInputMode('keyboard'))

這樣接了藍牙鍵盤的平板、或用觸控的筆電,都會在使用者實際操作的當下切到對的控制介面,而不是被開場的猜測鎖死。

斷點該怎麼訂

滿版遊戲的斷點與內容網站不同——你不是在排文字欄,而是在決定「UI 佈局模式」。實務上三個模式就夠:

模式條件特徵
compactpointer: coarse 且短邊 < 480px控制項貼底或貼側,UI 疊在畫布上
medium平板尺寸,或直向大螢幕控制項有獨立空間,可顯示次要資訊
expandedmin-width: 1024pxpointer: fine側欄常駐,畫布不被 UI 遮擋

min-width 而非 max-width(mobile-first,避免特異性打架),並且把模式寫成 CSS 自訂屬性讓 JS 也能讀:

:root { --ui-mode: 'compact'; }
@media (min-width: 768px) { :root { --ui-mode: 'medium'; } }
@media (min-width: 1024px) and (pointer: fine) { :root { --ui-mode: 'expanded'; } }

摺疊機

env(viewport-segment-width X Y)env(viewport-segment-height X Y) 提供各段視口的幾何,可用來避免把重要 UI 畫在鉸鏈上:

@media (horizontal-viewport-segments: 2) {
  .hud-center { display: none; }   /* 中央被鉸鏈切開,改用左右兩段 */
}

支援度仍在推進中,且「跨鉸鏈的遊戲畫面該怎麼取景」沒有業界共識,目前建議是把它當漸進增強處理。

檢查清單

  • pointerhover 判斷輸入方式,不用寬度
  • 觸控命中區 ≥ 44px,且不隨畫布 scale 縮小
  • 「請旋轉裝置」提示有加 (pointer: coarse) 條件
  • hover 專屬資訊在觸控裝置有替代呈現(長按/常駐)
  • 桌機不是單純放大手機 UI,有實質的資訊重排
  • 實測:接鍵盤的平板、觸控筆電、桌機拉窄視窗

下一篇處理真正的沉浸模式與它在 iOS 上的天花板 → 全螢幕與沉浸模式