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

ユーザーがサイトを移動できるよう、レスポンシブでアクセスしやすいパンくずリスト コンポーネントを作成する方法について、基本的な概要を説明します。

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

デモ

動画をご覧になる場合は、この投稿の YouTube バージョンをご覧ください。

概要

パンくずリスト コンポーネントは、ユーザーがサイト階層内のどの位置にいるかを示します。名前は Hansel と Gretel です。Hansel と Gretel は、暗い森の中にパンくずを落とし、そのくずを後方に辿って帰る道を見つけることができました。

この投稿のパンくずは、標準のパンくずリストではなく、パンくずリストのようなものです。<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>

サイト ナビゲーションには、暗黙的な ARIA のナビゲーション ロールが設定された <nav> 要素を使用することをおすすめします。テストの結果、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 使用要素が表示されている 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>

リンクといくつかのオプションは特別なものではなく、シンプルなパンくずリストの機能を強化します。title<select> 要素に追加すると、スクリーン リーダーのユーザーにとってボタンの操作に関する情報を提供するのに役立ちます。しかし、他の誰にとっても同様に役立ちます。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 コンポーネントは、グリッドを使用して SVG アイコンと「ほぼ目に見えない」<select> 要素とを重ね合わせます。

行と列の両方に名前付きスタックがあるボタンの上に重なって表示されている、グリッド 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 からスナップ ポイントを削除します。

メディアクエリ

小さいビューポートの場合の微調整の 1 つは、「ホーム」ラベルを非表示にしてアイコンだけを残すことです。

@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 の変更と eager <select> 変更イベントの呼び出し防止があります。

<select> 要素を使用しているため、積極的イベントの防止が必要です。Windows Edge では、おそらく他のブラウザでも、ユーザーがキーボードでオプションをブラウジングすると select changed イベントが発生します。そのため、ユーザーがカーソルを合わせる、フォーカスするなどの疑似選択をするにすぎず、enter または click で選択を確定していないため、積極的と呼んでいます。eager イベントにより、このコンポーネント カテゴリ変更機能にはアクセスできなくなります。これは、選択ボックスを開いて項目をブラウジングすると、ユーザーの準備ができる前にイベントが発生し、ページが変更されるためです。

より適切な <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> 要素のイベントが発生したときに待機するか続行するかを判断できます。

まとめ

私のやり方がわかったところで、どうしたらいいですか? 🙂?

多様なアプローチと、ウェブでの構築方法を学んでいきましょう。 デモを作成してツイートのリンクをお願いします。下のコミュニティ リミックス セクションに追加します。

コミュニティのリミックス