建立導覽標記元件

基礎總覽:如何建立回應式且無障礙的導覽標記元件,讓使用者能夠瀏覽您的網站。

透過這篇文章,我想分享一些建構導覽標記元件的思維。立即試用

示範

如果你偏好使用影片,也可以觀看這篇 YouTube 文章:

總覽

導覽標記元件會顯示使用者在網站階層中的哪個位置。名稱來自 Hansel and Gretel,他在一些深色樹後方放置導覽標記,並能回溯追蹤標記,找到家。

本文中的導覽標記不是標準導覽標記,而是類似導覽標記。這些程式庫使用 <select> 將同層級頁面直接放入導覽面板中,以提供多層級存取權。

背景使用者體驗

在上方的元件示範影片中,預留位置類別是電玩遊戲的類型。透過瀏覽下列路徑建立這條步道:home » rpg » indie » on sale,如下所示。

這個導覽標記元件應可讓使用者瀏覽這個資訊階層;跳轉分支版本並選取速度和準確度的網頁。

資訊架構

我發現可以考慮整理珍藏內容與項目。

集合

集合是指一系列選項可供選擇。從本文的導覽標記原型首頁中,系列包含 FPS、RPG、brawler、地牢檢索器、體育和拼圖。

項目

電玩遊戲是一個項目,如果某集合代表另一個集合,則特定集合也可以是項目。例如,RPG 是項目與有效的集合。如果是項目,使用者就會位在該集合頁面。舉例來說,這類遊戲位於 RPG 頁面,當中會顯示 RPG 遊戲清單,包括其他子類別 AAA、獨立和獨立發布子類別。

在電腦科學術語中,這個導覽標記元件代表一個多維度陣列

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

您的應用程式或網站會有自訂資訊架構 (IA),建立不同的多維度陣列,但我希望集合到達網頁和階層週遊的概念也能納入導覽標記中。

版面配置

標記

優秀的元件應從適當的 HTML 開始。下一節將說明我的標記選項 以及它們對整體元件的影響

深色和淺色配置

<meta name="color-scheme" content="dark light">

上述程式碼片段中的 color-scheme 中繼標記會讓瀏覽器知道這個網頁要採用淺色和深色瀏覽器樣式。範例中的導覽標記不含這些色彩配置的任何 CSS,因此導覽標記會使用瀏覽器提供的預設顏色。

<nav class="breadcrumbs" role="navigation"></nav>

建議您針對網站導覽使用 <nav> 元素,因為該元素具有隱含的 ARIA 導覽角色。我在測試中發現 role 屬性改變了螢幕閱讀器與元素的互動方式,實際上是將該屬性宣告為導覽功能,因此我選擇新增這個屬性。

圖示

在網頁上重複某個圖示時,SVG <use> 元素可讓您定義 path 一次,並用於圖示的所有例項。這樣可避免重複執行相同的路徑資訊,進而造成文件較大且路徑不一致。

如要使用這項技巧,請在頁面中加入隱藏的 SVG 元素,然後使用專屬 ID 將圖示納入 <symbol> 元素中:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

瀏覽器會讀取 SVG HTML,將圖示資訊放入記憶體中,然後繼續參照頁面其餘部分參照 ID,以進一步使用圖示,如下所示:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

顯示已轉譯 SVG 使用元素的開發人員工具。

請定義一次,無限次使用,同時將對網頁效能的影響降到最低,樣式也十分靈活。請注意,aria-hidden="true" 已新增至 SVG 元素。這對於只聽到內容的使用者來說並不實用,隱藏這些圖示可避免使用者看到不必要的雜訊。

這就是傳統的導覽標記和元件中其他屬性的不同。通常,這會是 <a> 連結,但我已在模擬的選取項目中新增週遊使用者體驗。.crumb 類別負責安排連結和圖示的版面配置,.crumbicon 則負責堆疊圖示並同時選取元素。我稱之為分割連結,因為其函式與分割按鈕非常類似,但適用於頁面導覽。

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

連結和部分選項並不特別,但可在簡單的導覽標記中加入更多功能。在 <select> 元素中加入 title 有助於螢幕閱讀器使用者瞭解按鈕動作的相關資訊。然而,對於其他使用者也同樣的情況,您可以看到它在 iPad 的正面和中央。其中一個屬性可為許多使用者提供按鈕的相關內容。

顯示懸停選取元素和內容工具提示的螢幕截圖。

分隔符裝飾

<span class="crumb-separator" aria-hidden="true">→</span>

分隔符為選用項目,只要加入一個分隔符就行了 (請參閱上方影片的第三個範例)。接著我分別提供每個 aria-hidden="true",因為它們是具裝飾性的,所以螢幕閱讀器不需要朗讀的內容。

以下將介紹 gap 屬性,讓這些項目的間距可直接呈現。

風格

由於色彩採用系統色彩,因此通常是樣式的缺口和堆疊!

版面配置方向和流程

開發人員工具顯示導覽標記導覽與其中的 Flexbox 重疊功能。

主要導覽元素 nav.breadcrumbs 會設定子項可使用的範圍自訂屬性,否則會建立水平對齊的版面配置。這可確保標記、分隔線和圖示對齊。

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

顯示一個與 Flexbox 重疊垂直對齊的導覽標記。

每個 .crumb 也會建立水平對齊的版面配置有一定間隔,但特別以連結子項為目標,並指定 white-space: nowrap 樣式。這對於多字詞導覽標記來說至關重要,因為我們不希望它們變成多行文字。本文稍後將新增樣式,處理這個 white-space 屬性造成的水平溢位。

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

系統會新增 aria-current="page",讓目前頁面的連結脫穎而出。這項功能不僅可讓螢幕閱讀器使用者明確知道連結前往目前網頁的原因,還採用了視覺樣式設定元素,協助視障使用者獲得類似的使用者體驗。

.crumbicon 元件會使用格線堆疊含有「極近隱藏」<select> 元素的 SVG 圖示。

格線開發人員工具顯示在按鈕上,且列和欄皆為已命名堆疊。

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

<select> 元素位於 DOM 的最後一個位置,因此位於堆疊上方,並且可以互動。新增 opacity: .01 樣式,讓元素仍可使用,且結果會是完全符合圖示形狀的選取方塊。這樣就能在維持內建功能的同時,自訂 <select> 元素的外觀。

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

溢位

導覽標記應能代表很長的路徑。我非常喜歡允許項目在適當情況下橫向移出畫面,而且我認為這個導覽標記元件相當完善。

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

溢位樣式會設定下列使用者體驗:

  • 使用過度捲動隔離的水平捲動。
  • 水平捲動邊框間距。
  • 最後一種標記就是一個貼齊點。這表示在載入網頁時,第一個標記會對齊且檢視畫面中載入。
  • 從 Safari 中移除貼齊點,因為這類效果與水平捲動和貼齊效果組合有所阻礙。

媒體查詢

如果是較小的可視區域,有一個細微的調整是隱藏「首頁」標籤,只保留圖示:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

並排對照包含及沒有首頁標籤的導覽標記,方便您比較。

無障礙功能

動作

這個元件中只有少量動作,但將轉場效果納入 prefers-reduced-motion 檢查中,可以防止不必要的動作。

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

其他樣式不需要變更,在不使用 transition 的情況下,懸停和聚焦效果都非常實用且有意義,但如果動作沒有問題,我們就會為互動加上細微轉場效果。

JavaScript

首先,無論您在網站或應用程式中使用的路由器類型為何,當使用者變更導覽標記時,網址就需要更新,且使用者會看到適當的頁面。第二,為了將使用者體驗正規化,請確保當使用者僅瀏覽 <select> 選項時,不會發生非預期的導覽。

JavaScript 處理的兩項關鍵使用者體驗措施:Select 已變更,並想要預防 <select> 變更事件觸發。

由於使用 <select> 元素,因此必須採取緊急事件預防措施。在 Windows Edge 上,可能也需要其他瀏覽器,當使用者使用鍵盤瀏覽選項時,系統會觸發選取 changed 事件。因此,我稱之為急著選擇,因為使用者僅有虛擬選取選項 (例如懸停或焦點),但尚未使用 enterclick 確認選擇。這類事件會導致此元件類別變更功能無法存取,因為如果您開啟選取方塊並瀏覽項目,系統會在使用者準備就緒前就觸發事件並變更頁面。

更合適的<select>變更活動

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

這項策略的策略是觀察每個 <select> 元素的鍵盤向下事件,並判斷按下的按鍵是否為瀏覽確認 (TabEnter) 或空間導覽 (ArrowUpArrowDown)。一旦確定,元件在 <select> 元素的事件啟動時,元件就可以決定要等待或結束。

結語

現在既然你已經知道我怎麼做,你會怎麼做‽ 🙂?

讓我們帶您更多元的方法,並瞭解運用網路打造網站的所有方式。 請建立示範並透過 Twitter 推文連結,我就能將這項工具新增至下方的「社群重混」部分!

社群重混作品