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

ユーザーがサイト内を移動するためのレスポンシブでアクセシブルなパンくずリスト コンポーネントを構築する方法の基本的な概要。

この記事では、パンくずリスト コンポーネントを構築する方法について説明します。デモを試す

デモ

動画で確認したい場合は、こちらの YouTube 版の投稿をご覧ください。

概要

パンくずリスト コンポーネントは、ユーザーがサイトの階層内のどこにいるかを示します。この名前は、暗い森の中でパンくずを落として、それをたどって家に帰った ヘンゼルとグレーテルに由来します。

この記事のパンくずリストは標準のパンくずリストではなく、パンくずリストのようなものです。<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 use 要素を示す DevTools。

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

従来のパンくずリストとこのコンポーネントのパンくずリストは、ここで分岐します。通常、これは <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 つの属性で、多くのユーザーにボタンのコンテキストを提供します。

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

区切り文字の装飾

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

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

次に説明する gap プロパティを使用すると、これらの間隔を簡単に設定できます。

スタイル

色にはシステムカラーを使用しているため、スタイルは主にギャップとスタックです。

レイアウトの方向とフロー

DevTools のパンくずリスト ナビゲーションの配置と、その flexbox オーバーレイ機能。

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

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

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

パンくずリストが 1 つ表示され、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 コンポーネントは、グリッドを使用して SVG アイコンと「ほぼ透明」な <select> 要素を重ねています。

行と列の両方が stack という名前のボタンにオーバーレイ表示された Grid 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> 要素を使用しているため、早期イベントの防止が必要です。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> 要素でキーボードのダウン イベントを監視し、押されたキーがナビゲーションの確認(Tab または Enter)か空間ナビゲーション(ArrowUp または ArrowDown)かを判断します。この判断に基づいて、<select> 要素のイベントが発火したときに、コンポーネントが待機するか続行するかを決定できます。

まとめ

私がどのように行ったかをご理解いただけたかと思います。では、あなたならどのようにしますか?🙂

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

コミュニティ リミックス