パンくずリスト コンポーネントの作成

ユーザーがサイト内を移動する際に、応答性が高くアクセスしやすいパンくずリスト コンポーネントを作成する方法について、基本的な概要を示します。

この投稿では、パンくずリスト コンポーネントを作成する方法についての考え方を共有します。デモをお試しください

デモ

動画で視聴したい場合は、この投稿の YouTube バージョンをご利用ください。

概要

パンくずリスト コンポーネントは、ユーザーがサイト階層内のどこにいるかを示します。この名前は Hansel and Gretel に由来しています。Hansel は、暗い森の中でパンくずを後ろから遡って帰宅する経路を見つけた人物です。

この投稿のパンくずリストは標準のパンくずリストではなく、パンくずのようなものです。また、<select> を使用して兄弟ページをナビゲーションに直接配置することで機能を追加し、多層アクセスを可能にします。

バックグラウンド UX

上のコンポーネント デモ動画では、プレースホルダ カテゴリはビデオゲームのジャンルです。このトレイルは、次のようにパス home » rpg » indie » on sale をナビゲートして作成されます。

このパンくずリストコンポーネントは、ユーザーがこの情報階層を移動し、ブランチをジャンプして迅速かつ正確にページを選択できるようにする必要があります。

情報アーキテクチャ

コレクションとアイテムの観点から考えると役に立つと思います。

コレクション

コレクションとは、選択できるオプションの配列です。この投稿のパンくずリスト プロトタイプのホームページには、FPS、RPG、ブラウラー、ダンジョン クローラー、スポーツ、パズルのコレクションが含まれています。

項目

ビデオゲームはアイテムですが、特定のコレクションが別のコレクションを表すアイテムである場合もあります。たとえば、RPG はアイテムであり、有効なコレクションです。アイテムの場合は、そのコレクション ページが表示されます。たとえば、RPG のページにアクセスして、さらに AAA、インディー、自己公開のサブカテゴリを含む RPG ゲームのリストを表示したとします。

コンピュータ サイエンスの用語では、このパンくずリスト コンポーネントは多次元配列を表します。

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 を 1 回定義すれば、そのアイコンのすべてのインスタンスに使用できます。これにより、同じパス情報が繰り返されることがなくなり、ドキュメントのサイズが大きくなり、パスに不整合が生じる可能性があります。

この方法を使用するには、非表示の 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 use 要素を表示している DevTools。

一度定義すれば何度でも使用でき、ページのパフォーマンスへの影響を最小限に抑え、柔軟なスタイル設定を実現できます。aria-hidden="true" が SVG 要素に追加されています。このアイコンは、コンテンツを音声しか聞いていないブラウジング ユーザーには役立ちません。そのようなユーザーに対して非表示にすることで、不要なノイズの追加を防ぐことができます。

ここが、従来のパンくずリストとこのコンポーネントのパンくずリストが分かれています。通常、これは <a> リンクのみですが、選択を偽装した走査 UX を追加しました。.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 では中心的に表示されます。1 つの属性で多くのユーザーにボタンのコンテキストを提供します。

非表示の選択要素がカーソルを合わせ、そのコンテキスト ツールチップが表示されているスクリーンショット。

区切りの装飾

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

セパレータは省略可能ですが、1 つだけ追加しても問題ありません(上の動画の 3 番目の例を参照)。次に、各 aria-hidden="true" は装飾的であり、スクリーン リーダーが読み上げる必要のないものであるため、指定します。

次に説明する gap プロパティは、これらの間隔を簡単に行えるようにします。

スタイル

この色はシステムカラーを使用しているため、ほとんどの場合、スタイルのギャップやスタックです。

レイアウト方向とフロー

Flexbox オーバーレイ機能を使用したパンくずリスト ナビゲーションの配置を示す DevTools。

プライマリ ナビゲーション要素 nav.breadcrumbs は、子が使用するスコープのカスタム プロパティを設定します。それ以外の場合は、水平方向の垂直配置のレイアウトを確立します。これにより、パンくず、分割線、アイコンが確実に配置されます。

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

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

Flexbox オーバーレイと縦に並んだ 1 つのパンくずリスト。

.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 アイコンを積み重ねます。

行と列の両方がスタック名であるボタンの上に重なったグリッド DevTools。

.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;
  }}
}

オーバーフロー スタイルにより、以下の UX が設定されます。

  • 水平スクロール(オーバースクロールの封じ込めあり)。
  • 水平スクロールのパディング。
  • 最後のパンくずにスナップ ポイントを 1 つ。つまり、ページの読み込み時に、最初のパンくずの読み込みがスナップされて表示されます。
  • 水平スクロールとスナップ エフェクトの組み合わせで苦労していた 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

まず、サイトまたはアプリケーションで使用しているルーターの種類に関係なく、ユーザーがパンくずリストを変更した場合は、URL を更新して適切なページを表示する必要があります。次に、ユーザー エクスペリエンスを正規化するために、ユーザーが <select> オプションを閲覧しているだけの場合に予期しないナビゲーションが発生しないようにします。

JavaScript で処理する 2 つの重要なユーザー エクスペリエンス対策です。SELECT の変更と、積極的な <select> 変更イベント発生の防止です。

積極的イベントの防止が必要なのは、<select> 要素を使用しているためです。Windows Edge では、おそらく他のブラウザでも、ユーザーがキーボードでオプションをブラウジングすると、select 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> 要素のキーボード ダウン イベントを監視し、押されたキーがナビゲーション確認(Tab または Enter)か空間ナビゲーション(ArrowUp または ArrowDown)かを判断することです。この決定により、コンポーネントは <select> 要素のイベントが発生したときに待機するか終了するかを決定できます。

おわりに

どのようにやり方をしたかわかったので、どのように感じますか? ‽ 🙂?

手法を多様化して、ウェブで構築するすべての方法を学びましょう。 デモを作成し、ツイートしてリンクをツイートしてください。以下のコミュニティ リミックスのセクションに追加いたします。

コミュニティ リミックス