空間導覽 (方向鍵盤焦點)

網頁功能: scrollIntoView()

方向焦點導覽 (使用方向鍵或 D-Pad) 可讓僅使用鍵盤和替代輸入的使用者,根據視覺 2D 版面配置瀏覽互動項目。這項功能非常適合用於空間使用者介面 (例如媒體導覽邊欄、資訊主頁面板或巢狀目錄),但必須謹慎導入,以免破壞原生瀏覽器行為 (例如可視區域捲動)。

本指南說明如何在複合小工具中,建構高效能、無障礙的 2D 方向導覽。


核心架構原則

實作自訂方向鍵空間焦點時,請遵循下列基本規則:

  1. 僅限複合小工具:請勿在一般文件文字流程或簡單的垂直清單版面配置中覆寫方向鍵,因為這些版面配置應使用預設的依序 Tab 鍵導覽。如需一般文件流程指南,請參閱無障礙功能編碼指南。
  2. 保留原生邊界捲動:如果按下方向沒有可聚焦的元素,請勿阻止預設瀏覽器行為。讓事件向上傳遞,以便瀏覽器以原生方式捲動可視區域。無條件覆寫方向鍵會破壞無障礙功能 (WCAG 1.4.10)。
  3. 確保焦點可見度和對齊方式:焦點變更時,請確保新焦點元素捲動至檢視畫面 (例如使用 scrollIntoView({ block: 'nearest', inline: 'nearest' })),以便完整顯示。
  4. 與版面配置系統協調:在處理每次按鍵動作時,從即時邊界幾何圖形 (getBoundingClientRect()) 判斷焦點候選位置。請勿假設 DOM 順序或固定格線結構代表視覺版面配置。如需現代版面配置建議,請參閱「CSS 版面配置和回應式設計」。

無障礙焦點管理

空間容器必須為依序頁面分頁導覽提供單一分頁停駐點。在小工具中,使用輪流 Tabindex 模式管理鍵盤焦點:

  • 有效項目有 tabindex="0"。
  • 所有閒置項目都有 tabindex="-1"。
  • 瀏覽時,請透過程式輔助方式移動焦點,方法是移動 tabindex="0" 屬性並呼叫 focus()。

視覺候選項目選取

在不對稱、換行或網格版面配置中,以索引為基礎的對應會失敗。請改為從目前按鍵的即時視覺幾何圖形衍生候選字:

  • 候選:使用通過 checkVisibility() 的小工具容器可聚焦子系。隱藏元素在 (0, 0) 處可以有大小為零的矩形,且不得意外成為 up 或 left 目標。
  • 幾何圖形:處理按鍵時,讀取焦點元素和每個候選元素的 getBoundingClientRect()。請勿全域快取這些矩形:版面配置可能會因多種原因而變更,且在處理使用者步調的按鍵事件時,少量幾何圖形讀取作業的成本較低。
  • 方向篩選器:只有在候選元素的近端邊緣位於或超出目前元素在該軸上的遠端邊緣時,候選元素才會位於按下的方向,允許子像素四捨五入的小容差。舉例來說,right 的候選字詞必須包含 candidate.left >= current.right - tolerance。請勿比較中心點,判斷候選項目是否位於該方向。
  • 優先選擇對齊的候選元素:在通過方向篩選器的候選元素中,優先選擇垂直軸範圍與目前元素範圍重疊的候選元素。選擇導覽軸上最小的間隙,並以最小的垂直中心偏移量打破平局。即使項目異常寬,導覽仍會保持在相鄰的視覺列或欄中。
  • 備用評分:如果沒有任何候選項目在垂直軸上重疊,請將剩餘候選項目評分為 gap + w * perpendicularCentreOffset,並選擇最小值。請先使用權重 w >= 2,再根據版面配置進行調整;權重越大,越有利於對齊程度較高的候選項目。
  • 無候選項目:不傳回任何內容。事件處理常式不得呼叫 preventDefault(),讓瀏覽器以原生方式捲動。

確切實作方式可能有所不同,但請務必以邊緣為基礎進行方向測試,並在套用加權備用廣告前,先套用重疊偏好設定。這是演算法的核心,請避免將快取或固定權重視為普遍正確。


按鍵整合與捲動協作

事件路由器「只能」在發生有效的空間焦點轉換時,防止標準可視區域捲動:

  • 如果按住輔助鍵、e.defaultPrevented 已為 true,或最內層的事件目標本身會使用方向鍵,請勿攔截事件。請使用 e.composedPath()[0],而非僅使用 e.target,因為 e.target 可以重新指定至 Shadow 主機。實用的可編輯/控制項檢查是 target.matches(':read-write, select, input[type="range"], [role="slider"]');無法檢查封閉的陰影根。
  • 將事件監聽器範圍限定在小工具容器,這樣多個空間小工具就能在同一個頁面並存。
  • 只有在焦點元素本身是候選元素時,才執行動作。如果項目包含可聚焦的控制項,請將這些控制項也設為候選項目,或不予處理;請勿使用 closest() 解析父項。
  • 找到候選項目後,請呼叫 preventDefault()、更新漫遊 tabindex、呼叫 focus(),並使用 scrollIntoView({ block: 'nearest', inline: 'nearest' })。
  • 如果找不到候選項目,則不呼叫 preventDefault() 直接傳回,讓原生捲動功能繼續運作。

請避免新增單一字元快速鍵 (例如 h/j/k/l 或 W/A/S/D) 進行空間導覽。方向鍵不是字元鍵,在沒有候選字時保留原生捲動功能,可避免不必要地阻擋可視區域移動。