Codelab: Sidenav コンポーネントをビルドする

この Codelab では、ウェブ上でレスポンシブなスライド アウト サイド ナビゲーション レイアウト コンポーネントを作成する方法について説明します。コンポーネントは、HTML、CSS、JavaScript の順に作成していきます。

このコンポーネントの構築に選択された CSS ウェブ プラットフォームの機能については、ブログ投稿「サイドナビ コンポーネントの構築」をご覧ください。

セットアップ

  1. [Remix to Edit] をクリックして、プロジェクトを編集可能にします。
  2. app/index.html を開きます。

HTML

まず、コンテンツと操作するボックスを用意するために、HTML の設定の基本を理解します。

次の HTML を <body> タグにドロップします。

<aside></aside>
<main></main>

<aside> は、メインのページ コンテンツを保持する <main> の補足要素としてナビゲーション メニューを保持します。

次に、これらのセマンティック要素にページの残りのコンテンツを入力します。

<aside> 要素内にナビゲーション要素、ナビゲーション リンク、閉じるリンクを追加します。

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

リンクは <nav> 要素内に配置すると効果的です。また、<nav> 要素は <aside> サイドバーに配置すると効果的です。それでも、改善の余地はまだあります。

メイン コンテンツ要素に、レイアウト コンテンツを意味的に保持するヘッダーと記事を追加します。

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

ヘッダーにメニューを開くリンクがあります。aside には閉じるボタンがあります。まもなく、ビューポートのサイズに基づいて要素の表示と非表示を切り替えられるようになります。

<article> 要素に、プレースホルダの文を貼り付けました。`` を独自の内容に置き換えるか、以下の lorem を貼り付けます。

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

このコンテンツとその長さによって、ビューポートの高さを超えた場合にページがスクロール可能になります。

これまでに、ナビゲーション、リンク、サイドナビを閉じる方法を含む aside 要素を追加しました。また、ヘッダー、サイドナビを開く方法、記事をメイン要素に追加しました。このコードはクリーンでセマンティックであり、すでにかなり時代に左右されないものですが、さらにクリーンでわかりやすいものにすることができます。サイドナビのリンクを開くボタンがもっとわかりやすく表示されるようにしてほしい。

ヘッダーのリンクを開く要素に属性 titlearia-label を追加します。

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

開いている SVG アイコンも、より明確にマークされる可能性があります。オープンリンク要素内の SVG に次の属性を追加します。

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

サイドナビの閉じるリンクがもっとわかりやすく表示されるようにしてください。 サイドナビの閉じるリンク要素に属性 titlearia-label を追加します。

<a href="#"></a>
<a href="#" title="Close Menu" aria-label="Close Menu"></a>

CSS

要素をレイアウトします。メイン コンテンツとサイドナビは <body> タグの直接の子であるため、ここから始めるのが適切です。

css/sidenav.css に次の CSS を追加して、<body> 要素が子要素をレイアウトするようにします。

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

このレイアウトは、基本的に「stack という名前の行を作成し、その行にすべてを配置する。その行に 2 つの列を配置し、2 つ目の列にも stack という名前を付ける」という意味になります。1 列目は最小限のコンテンツの必要性に応じてサイズ設定し、2 列目は残りのスペースを占有できます。次に、ビューポートが 540px 以下に制限されている場合は、サイドナビとメイン コンテンツの要素を同じ行と列に配置し、1x1 グリッドで互いに重ねて表示します。

このレスポンシブなスタック機能をベースとして、URL バーの状態を利用してサイドナビの表示とトランジション スタイルを切り替えることができるようになりました。

app/index.html<aside> 要素を更新します。

<aside>
<aside id="sidenav-open">

これにより、CSS で要素と URL ハッシュを一緒に照合できます。これは :target の使用において重要です。これで、要素の ID は <a> タグで設定する URL ハッシュと一致するようになります。

また、JavaScript のターゲット設定を容易にするため、サイドナビを制御するキー要素の ID を追加します。まず、サイドナビの開くリンクに ID を追加します。

<a href="#sidenav-open" class="hamburger" title="Open Menu" aria-label="Open Menu">
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">

次に、サイドナビの閉じるリンクに ID を追加します。

<a href="#" title="Close Menu" aria-label="Close Menu"></a>
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

これで、マクロ <body> レスポンシブ スタッキング レイアウトが完成し、URL バーにリンクされました。先に進みましょう。

<aside> もすっきりとしたレイアウトになっています。この要素には 2 つの子要素があります。1 つは紙のような外観のスライド アウト コンポーネントである <nav>、もう 1 つは URL を # に設定する閉じる <a> リンク要素です。リンクは、ペーパー スライドアウト ナビゲーションの右側に表示されません。これは、ユーザーがビジュアル コンポーネントを「クリックしてオフ」にすることで、コンポーネントを閉じられるようにするためです。

次の CSS を css/sidenav.css に追加します。

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

比率と名前が非常に優れており、グリッドが輝き、デザイナーに多くの制御を提供できると思いました。

次に、メイン コンテンツを条件付きでオーバーレイし、ドキュメントのスクロールを通じて位置を保持する必要があります。これは、position: sticky と一部の overscroll-behavior に適したジョブです。

サイドナビに次のスタイルを追加します。

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

これらのスタイルにより、サイドナビがビューポートの高さになり、垂直方向にスクロールしてスクロールが含まれるようになります。非常に重要な点として、要素が非表示になります。デフォルトでは、ビューポートが 540px 以下の場合は、そのサイドナビを非表示にします。ただし、

#sidenav-open 要素に :target 疑似セレクタを追加します。

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

その要素の ID と URL バーが同じ場合は、visibilityvisible に設定します。ページをスクロールしてからサイドメニューを開くか、サイドナビが開いている間にページをスクロールしてみてください。ご意見をお聞かせください

次の CSS を app/sidenav.css の末尾に追加します。

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

これらのスタイルは、開閉ボタンを対象とし、タップとタッチのスタイルを指定します。また、ビューポートが 540px 以上の場合はボタンを非表示にします。

アクセシビリティに配慮した CSS 変換を追加して、少しだけ装飾しましょう。次の CSS を css/sidenav.css に追加します。

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
`prefers-reduced-motion` メディアクエリに基づいて、期間が適用されている場合と適用されていない場合のインタラクションのデモ。

JavaScript を追加する

Escape キーでメニューを閉じます。js/index.js に次の JS を追加します。

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

これは、サイドナビ要素のキーイベントをリッスンします。Escape の場合、URL ハッシュを空に設定して、サイドナビを非表示にします。

次の UX JS は、フォーカス管理です。開閉を簡単にするため、サイドナビが何らかの遷移を完了するまで待機し、URL ハッシュと照合して、開いているか閉じているかを判断します。次に、JavaScript を使用して、押したボタンの補完的なボタンにフォーカスを設定します。

js/index.js に次の JavaScript を追加します。

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

試してみる

  • サイトをプレビューするには、[アプリを表示] を押し、[全画面表示] 全画面表示 を押します。

まとめ

コンポーネントに関する私のニーズは以上です。このコードをベースに、URL ではなく JavaScript の状態を使用して駆動するなど、自由にカスタマイズしてください。追加すべきことや、対応すべきユースケースは常にあります。

css/brandnav.css を開いて、このコンポーネントに適用したレイアウト以外のスタイルを確認してください。私が重視していた機能セットでは重要ではないと感じました。また、スタイルとレイアウトを分離することで、コピー&ペーストが促進されることを期待していました。さらに学習を進めることができます。

スライド アウトするレスポンシブ サイドナビ コンポーネントを作成するにはどうすればよいですか?両側に 1 つずつなど、2 つ以上になることはありますか?あなたの解決策を YouTube 動画で紹介したいので、コードを ツイートするか、YouTube でコメントしてください。皆さんの役に立つと思います。