「画面全体をメニューで覆うフルスクリーンナビゲーション、かっこいいけど、どうやって作るの?」——ポートフォリオサイトやクリエイティブ系サイトで見かけるあのメニュー、実はライブラリなしでCSSとJavaScriptだけで作れます。

この記事では、フルスクリーンメニューの作り方を要点に絞って解説します。フェードインで画面全体を覆い、リンクが1つずつスライドアップで出現する「スタッガードアニメーション」が見どころです。

実際の動作はこちら

🌐 デモを見る(GitHub Pages)

💻 ソースコード全文(GitHub)

コード全文はGitHubリポジトリで公開しているので、本記事では動作の核になる部分だけを抜粋して解説します。全部のコードを頭に入れる必要はありません。仕組みを理解して、安心してコピペできるようになることがゴールです。


このスニペットで作れるもの

このスニペットで作れるのは、画面全体をダークな背景で覆うフルスクリーンメニューです。スマホではハンバーガーボタンをタップすると暗い背景が画面いっぱいに広がり、中央にリンクが大きめのフォントで縦に並びます。リンクはフェードイン+スライドアップで1つずつ順番に出現します。

ヘッダーはメニューの「上」に浮かぶ設計で、ロゴとハンバーガーボタン(×形)が白色に切り替わります。PCサイズ(768px以上)では通常の横並びナビに切り替わり、フルスクリーンメニューは表示されません。

動作の特徴をまとめると次のとおりです。

  • 画面全体を覆うフルスクリーンメニュー(フェードインで表示)
  • メニューリンクが1つずつスライドアップで出現(スタッガードアニメーション)
  • ヘッダー背景が透明になり、ロゴ・×ボタンが白色に変化
  • Escapeキーでメニューを閉じられる
  • メニュー開放中はページスクロールが止まる(スクロールロック)
  • Tabキーのフォーカスがメニュー内でループする(フォーカストラップ)
  • 768px以上でPCナビに切り替わり、フルスクリーンメニューは非表示
  • aria属性によるアクセシビリティ対応

実際の動きはGitHub Pagesのデモで確認できます。スマホサイズに近づけたり広げたりしながら触ってみると、このあとの解説が一気に分かりやすくなりますよ。


HTMLの構造を見てみよう

HTMLのポイントは、「ヘッダー(c-header)」と「フルスクリーンナビ(c-fullscreen-nav)」が分かれている構造です。ハンバーガーボタンはヘッダー内に、フルスクリーンメニューはヘッダーの外に置いています。

<!-- ヘッダー -->
<header class="c-header">
  <div class="c-header__inner">
    <a href="#" class="c-header__logo">LOGO</a>

    <!-- ハンバーガーボタン(モバイルのみ表示) -->
    <button
      class="c-hamburger"
      type="button"
      aria-label="メニューを開く"
      aria-expanded="false"
      aria-controls="c-fullscreen-nav"
    >
      <span class="c-hamburger__line"></span>
      <span class="c-hamburger__line"></span>
      <span class="c-hamburger__line"></span>
    </button>

    <!-- PCナビ(省略) -->
  </div>
</header>

<!-- フルスクリーンナビゲーション(モバイル: 画面全体を覆うメニュー) -->
<nav
  class="c-fullscreen-nav"
  id="c-fullscreen-nav"
  aria-label="フルスクリーンナビゲーション"
  aria-hidden="true"
>
  <ul class="c-fullscreen-nav__list">
    <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">TOP</a></li>
    <!-- 他のリンクは省略 -->
  </ul>
</nav>

クラス名のプレフィックスには、FLOCSS(フロックス)というCSS設計ルールを採用しています。c- はComponent(再利用できる部品)を意味します。ヘッダーもハンバーガーボタンもフルスクリーンナビも、それぞれ独立した部品として設計されています。

ハンバーガーボタンには <div> ではなく <button> タグを使います。キーボード操作やスクリーンリーダーへの対応が自動的に行われるため、アクセシビリティ上のベストプラクティスです。

フルスクリーンナビを <header> の外に置く理由

フルスクリーンナビは <header> の外に配置しています。理由は、ヘッダーをフルスクリーンメニューの「上」に表示させるためです。

z-index(重なり順)の階層を見ると、構造が分かりやすくなります。z-indexの詳細はこのあとのCSSセクションで解説しますが、ここでは全体像を掴んでおいてください。

要素z-index役割
c-fullscreen-nav--z-overlay: 30画面全体を覆うメニュー
c-header--z-drawer: 40メニューの上に浮かぶヘッダー(フルスクリーンメニューの上にヘッダーを表示するため、通常よりも高いz-index値を割り当てています)

c-fullscreen-nav(z-index: 30)が画面全体を覆い、その上に c-header(z-index: 40)が浮かぶ構造です。これにより、メニューが開いた状態でもロゴと×ボタンが常にクリックできます。

z-indexの値はCSSカスタムプロパティ(--z-overlay / --z-drawer)で一元管理しています。数値が散らばると「どれが上でどれが下か」が分からなくなりがちですが、変数にまとめておけば見通しが良くなります。

aria属性の役割

aria属性は、スクリーンリーダーなどの支援技術に対して「この要素が今どんな状態か」を伝えるための属性です。このスニペットでは以下のaria属性を使っています。

属性役割
aria-label="メニューを開く"ボタンの名前をスクリーンリーダーに伝える(ボタン内にテキストがないため)
aria-expanded="false"メニューが閉じていることを示す(開いたとき "true" に変わる)
aria-controls="c-fullscreen-nav"このボタンが操作する要素のIDを示す(id="c-fullscreen-nav" と対応)
aria-hidden="true"(フルスクリーンナビ側)非表示のとき「この要素は読まなくていい」と伝える

最初は難しく感じるかもしれませんが、このスニペットをコピペするだけでアクセシビリティ対応ができます。慣れてきたら「どうしてこう書くのか」を一つずつ確認してみてください。


CSSでフルスクリーンメニューとアニメーションを実装する

CSS全文は長いため、GitHubリポジトリに譲り、ここでは動きの核になるポイントだけを抜粋して解説します。

論理プロパティ(margin-block / padding-inline)について

CSS全文を覗くと margin-blockpadding-inline といった見慣れない書き方が出てきます。これは「論理プロパティ」と呼ばれる比較的新しいCSSの書き方で、従来の margin-top / margin-bottompadding-left / padding-right の一括指定に相当します。横書きの日本語サイトであれば、動作は従来のプロパティと同じです。「最近のコードでよく見かける書き方」として覚えておけば問題ありません。

フルスクリーンナビの表示切り替え — opacity + visibility でフェード

フルスクリーンナビの表示・非表示には display: none ではなく、opacityvisibility の組み合わせを使います。display: none では要素が瞬時に消えるため、フェードイン・フェードアウトのアニメーションが効きません。opacity + visibility なら transition と組み合わせてなめらかに表示・非表示を切り替えられます。

/* フルスクリーンナビ: 初期状態(非表示) */
.c-fullscreen-nav {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background-color: var(--fullscreen-bg);

  display: flex;
  align-items: center;
  justify-content: center;

  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--fullscreen-transition),
    visibility var(--fullscreen-transition);
}

/* is-open クラスでフルスクリーン表示 */
.c-fullscreen-nav.is-open {
  opacity: 1;
  visibility: visible;
}

position: fixed; inset: 0 で画面全体を覆い、display: flex + align-items: center + justify-content: center でメニューリンクを中央に配置しています。inset: 0top: 0; right: 0; bottom: 0; left: 0 の一括指定です。

JavaScriptから is-open クラスを付けるだけで、CSSの transition が自動的にフェードアニメーションを処理してくれます。

リンクの順次スライドアップ(スタッガードアニメーション)

フルスクリーンメニューの見どころが、リンクが1つずつ順番にスライドアップで出現する「スタッガードアニメーション」です。

各リンク(.c-fullscreen-nav__item)の初期状態を opacity: 0; transform: translateY(20px)(下に20pxずらして透明)に設定し、is-open 時に opacity: 1; transform: translateY(0)(元の位置に戻して表示)へ切り替えます。

ポイントは transition-delay です。各アイテムに段階的な遅延を付けることで「順番に出てくる」動きが生まれます。

/* 各リンクの初期状態 */
.c-fullscreen-nav__item {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity var(--fullscreen-transition),
    transform var(--fullscreen-transition);
  /* 閉じるときは遅延なしで即座にフェードアウト */
  transition-delay: 0s;
}

/* is-open 時: 表示 */
.c-fullscreen-nav.is-open .c-fullscreen-nav__item {
  opacity: 1;
  transform: translateY(0);
}

/* 各アイテムに遅延を付けてスタッガードアニメーションにする */
.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(1) {
  transition-delay: calc(var(--fullscreen-item-delay) * 1);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(2) {
  transition-delay: calc(var(--fullscreen-item-delay) * 2);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(3) {
  transition-delay: calc(var(--fullscreen-item-delay) * 3);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(4) {
  transition-delay: calc(var(--fullscreen-item-delay) * 4);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(5) {
  transition-delay: calc(var(--fullscreen-item-delay) * 5);
}

--fullscreen-item-delay はカスタムプロパティ(初期値 0.06s)で管理しているので、calc(var(--fullscreen-item-delay) * n) で1番目は0.06秒後、2番目は0.12秒後…と順番に表示されます。

閉じるときは transition-delay: 0s(初期状態側に記載)が適用されるため、全リンクが一斉にフェードアウトします。開くときだけ「順番に」、閉じるときは「一斉に」という動きの違いがここで生まれています。CSS全文では詳細度の関係で記載順が異なりますが、動作は同じです。

ヘッダー背景の透明化とロゴ・ボタンの白色化

フルスクリーンメニューが開くと、ヘッダーの背景を透明にし、ロゴとハンバーガーボタンの色を白に変えます。ダークな背景の上に白いUI要素が浮かぶデザインです。

/* フルスクリーンメニュー展開時: ヘッダー背景を透明に */
.c-header.is-menu-open {
  background-color: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
}

/* ロゴを白色に */
.c-header.is-menu-open .c-header__logo {
  color: #fff;
}

/* ハンバーガーボタンの線を白色に */
.c-hamburger.is-active .c-hamburger__line {
  background-color: #fff;
}

ヘッダー自体は z-index: var(--z-drawer) でフルスクリーンナビの上にいるため、背景を透明にしてもロゴと×ボタンはクリックできる状態を保っています。

×(バツ)変形アニメーションの仕組み

ハンバーガーボタンのアニメーションは、3本線それぞれに transform をかけて×形に組み替えるシンプルな作りです。

/* 1本目: 下に7px下がって45度回転 */
.c-hamburger.is-active .c-hamburger__line:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

/* 2本目: 非表示 */
.c-hamburger.is-active .c-hamburger__line:nth-child(2) {
  opacity: 0;
}

/* 3本目: 上に7px上がって -45度回転 */
.c-hamburger.is-active .c-hamburger__line:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

translateY(7px) の数値には根拠があります。線の高さ(height: 2px)と、線間のギャップ(gap: 5px)を足すと 2 + 5 = 7px になります。この距離だけ上下に動かすことで、1本目と3本目が中央の線の位置で交差して×形になります。

フルスクリーンメニュー固有のポイントとして、×形になったとき線の色が白に変わります。ダークな背景に白い×ボタンが映えるデザインです。


JavaScriptの実装を見てみよう

JavaScript全文はGitHubリポジトリに譲り、ここでは核となる処理に絞って解説します。

openMenu / closeMenu の役割

メニューの開閉はそれぞれ専用の関数に分けています。

function openMenu() {
  hamburger.classList.add("is-active");
  fullscreenNav.classList.add("is-open");
  header.classList.add("is-menu-open");

  // aria 属性更新
  hamburger.setAttribute("aria-expanded", "true");
  hamburger.setAttribute("aria-label", "メニューを閉じる");
  fullscreenNav.setAttribute("aria-hidden", "false");

  // スクロールロック
  document.body.style.overflow = "hidden";

  // フルスクリーンナビ内の最初のリンクにフォーカス移動
  const firstFocusable = fullscreenNav.querySelectorAll(FOCUSABLE_SELECTOR)[0];
  if (firstFocusable) {
    setTimeout(function () {
      firstFocusable.focus();
    }, 400);
  }
}

function closeMenu() {
  hamburger.classList.remove("is-active");
  fullscreenNav.classList.remove("is-open");
  header.classList.remove("is-menu-open");

  // aria 属性更新
  hamburger.setAttribute("aria-expanded", "false");
  hamburger.setAttribute("aria-label", "メニューを開く");
  fullscreenNav.setAttribute("aria-hidden", "true");

  // スクロールロック解除
  document.body.style.overflow = "";
}

openMenu ではクラス付与、aria属性更新、スクロールロック、フォーカス移動を行います。closeMenu ではその逆の処理です。

ヘッダー(c-header)にも is-menu-open クラスを付与しているのがフルスクリーン固有のポイントです。このクラスをトリガーにして、CSSでヘッダー背景の透明化やロゴの白色化を行っています。

関数を分けている理由は、Escapeキーやリサイズなど複数の場所から closeMenu() を呼び出す必要があるためです。1つにまとめておくことで、コードの重複をなくせます。

スクロールロックの仕組み

メニューが開いている間は、背景のページスクロールを止める必要があります。フルスクリーンで画面全体を覆っているため、背景がスクロールしてしまうと違和感のある動きになります。

// メニューを開くとき
document.body.style.overflow = "hidden";

// メニューを閉じるとき
document.body.style.overflow = "";

overflow: "hidden" でスクロールを止め、"" の代入でインラインスタイルを除去してデフォルトの挙動に戻しています。

iOSでの注意点

iOSのSafariでは overflow: hidden だけではスクロールが止まらないケースがあります。実案件で対応する場合は、position: fixed + スクロール位置の保存・復元を組み合わせる方法が安定します。このスニペットではシンプルさを優先して overflow のみの実装としています。

フォーカストラップ — ハンバーガーボタンもトラップ範囲に含める

フォーカストラップとは、Tabキーのフォーカスをメニュー内に閉じ込める仕組みです。メニューが開いているのに、Tabキーでメニューの外にフォーカスが移ってしまうと、キーボード操作のユーザーが迷子になります。

フルスクリーンメニューのフォーカストラップで特徴的なのは、ハンバーガーボタンをトラップ範囲に含めている点です。フルスクリーンメニューではヘッダーがメニューの上に浮いているため、×状態のハンバーガーボタンもフォーカス対象に含める必要があります。

function trapFocus(e) {
  if (e.key !== "Tab") return;
  if (!fullscreenNav.classList.contains("is-open")) return;

  // ハンバーガーボタン + フルスクリーンナビ内のフォーカス可能要素を結合
  const navFocusable = Array.from(fullscreenNav.querySelectorAll(FOCUSABLE_SELECTOR));
  const focusableElements = [hamburger, ...navFocusable];
  if (focusableElements.length === 0) return;

  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  if (e.shiftKey) {
    // Shift + Tab: 最初の要素でさらに後退 → 最後の要素に移動
    if (document.activeElement === firstElement) {
      e.preventDefault();
      lastElement.focus();
    }
  } else {
    // Tab: 最後の要素でさらに前進 → 最初の要素に移動
    if (document.activeElement === lastElement) {
      e.preventDefault();
      firstElement.focus();
    }
  }
}

const focusableElements = [hamburger, ...navFocusable] で、ハンバーガーボタンを配列の先頭に追加しています。これにより、×ボタン → メニューリンク1 → メニューリンク2 → … → 最後のリンク → ×ボタン…とフォーカスがループします。フルスクリーン上のすべてのUI要素がキーボードで操作できる設計です。

Escapeキー・リサイズ対応

Escapeキーでメニューを閉じ、フォーカスをハンバーガーボタンに戻します。「閉じた後にフォーカスを戻す」のは小さなポイントですが、キーボード操作のユーザーにとって「フォーカスが迷子になる」のを防ぐ大事な配慮です。

画面幅のリサイズは window.matchMedia("(min-width: 768px)") で検知します。PCサイズに広がったらメニューを閉じてスクロールロックを解除し、aria-hidden 属性自体を除去します。PCでは横並びナビが常時表示されるため、「非表示状態」を示す aria-hidden は不要だからです。

スマホサイズに戻ったときは aria-hidden="true" を再度付与し、非表示であることをスクリーンリーダーに伝えます。


コピペで使えるソースコード全文

HTML・CSS・JSの全文は、GitHubリポジトリで公開しています。そのままコピペして使える状態で置いていますので、手元のプロジェクトに取り込んでお使いください。

コピペ用:HTML / CSS / JS 全文

ここからコピペで使える完全なソースコードを掲載しています。GitHubを使わない方はこちらをそのままコピーしてください。HTML・CSS・JSの3ファイル構成で、そのまま動作します。

HTML(index.html)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>003_hamburger-fullscreen</title>
    <link rel="stylesheet" href="./assets/css/style.css" />
  </head>
  <body>
    <header class="c-header">
      <div class="c-header__inner">
        <!-- ロゴ -->
        <a href="#" class="c-header__logo">LOGO</a>

        <!-- ハンバーガーボタン(モバイルのみ表示) -->
        <button
          class="c-hamburger"
          type="button"
          aria-label="メニューを開く"
          aria-expanded="false"
          aria-controls="c-fullscreen-nav"
        >
          <span class="c-hamburger__line"></span>
          <span class="c-hamburger__line"></span>
          <span class="c-hamburger__line"></span>
        </button>

        <!-- ナビゲーション(PC: 横並び表示) -->
        <nav class="c-nav" aria-label="グローバルナビゲーション">
          <ul class="c-nav__list">
            <li class="c-nav__item"><a href="#" class="c-nav__link">TOP</a></li>
            <li class="c-nav__item"><a href="#" class="c-nav__link">ABOUT</a></li>
            <li class="c-nav__item"><a href="#" class="c-nav__link">WORKS</a></li>
            <li class="c-nav__item"><a href="#" class="c-nav__link">BLOG</a></li>
            <li class="c-nav__item"><a href="#" class="c-nav__link">CONTACT</a></li>
          </ul>
        </nav>
      </div>
    </header>

    <!-- フルスクリーンナビゲーション(モバイル: 画面全体を覆うメニュー) -->
    <nav
      class="c-fullscreen-nav"
      id="c-fullscreen-nav"
      aria-label="フルスクリーンナビゲーション"
      aria-hidden="true"
    >
      <ul class="c-fullscreen-nav__list">
        <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">TOP</a></li>
        <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">ABOUT</a></li>
        <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">WORKS</a></li>
        <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">BLOG</a></li>
        <li class="c-fullscreen-nav__item"><a href="#" class="c-fullscreen-nav__link">CONTACT</a></li>
      </ul>
    </nav>

    <script src="./assets/js/script.js" defer></script>
  </body>
</html>

CSS(assets/css/style.css)

/* ================================================
   カスタムプロパティ
   ================================================ */
:root {
  --header-height-sp: 60px;
  --header-height-pc: 72px;
  --fullscreen-bg: #1a1a1a;
  --fullscreen-transition: 0.4s ease;
  --fullscreen-item-delay: 0.06s;

  /* ===== z-index 階層管理 ===== */
  --z-overlay: 30; /* フルスクリーンナビ(オーバーレイとして機能) */
  --z-drawer:  40; /* ヘッダー(メニューより上に表示) */
}

/* ================================================
   c-header: ヘッダー全体
   ================================================ */
.c-header {
  position: sticky;
  top: 0;
  z-index: var(--z-drawer);
  background-color: #fff;
  border-bottom: 1px solid #e0e0e0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
  transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

/* フルスクリーンメニュー展開時: ヘッダー背景を透明にして×ボタン(白)を見せる */
.c-header.is-menu-open {
  background-color: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
}

.c-header.is-menu-open .c-header__logo {
  color: #fff;
}

.c-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: calc(1200px + 20px * 2);
  margin-inline: auto;
  padding-inline: 20px;
  height: var(--header-height-sp);
}

@media (min-width: 768px) {
  .c-header__inner {
    padding-inline: 40px;
    height: var(--header-height-pc);
  }
}

.c-header__logo {
  font-size: 20px;
  font-weight: bold;
  color: #333;
  text-decoration: none;
  letter-spacing: 0.05em;
}

@media (min-width: 768px) {
  .c-header__logo {
    font-size: 24px;
  }
}

/* ================================================
   c-hamburger: ハンバーガーボタン
   ================================================ */
.c-hamburger {
  /* ボタンリセット */
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  margin: -8px;

  /* 3本線を縦に並べるレイアウト */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;

  position: relative;
}

@media (min-width: 768px) {
  /* PCでは非表示 */
  .c-hamburger {
    display: none;
  }
}

/* ハンバーガーの1本線 */
.c-hamburger__line {
  display: block;
  width: 24px;
  height: 2px;
  background-color: #333;
  border-radius: 2px;
  transform-origin: center;
  transition:
    transform 0.3s ease,
    opacity 0.3s ease,
    background-color 0.3s ease;
}

/* メニューオープン時: 線の色を白に変更(フルスクリーン背景に対応) */
.c-hamburger.is-active .c-hamburger__line {
  background-color: #fff;
}

/* ×(バツ)アニメーション */
.c-hamburger.is-active .c-hamburger__line:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.c-hamburger.is-active .c-hamburger__line:nth-child(2) {
  opacity: 0;
}

.c-hamburger.is-active .c-hamburger__line:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ================================================
   c-nav: PCナビゲーション(横並び)
   ================================================ */
.c-nav {
  /* モバイル: 非表示 */
  display: none;
}

@media (min-width: 768px) {
  /* PC: 常に表示(横並び) */
  .c-nav {
    display: block;
  }
}

.c-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 768px) {
  .c-nav__list {
    display: flex;
    gap: 32px;
  }
}

.c-nav__link {
  display: block;
  padding: 0;
  color: #333;
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.05em;
  transition: color 0.2s ease;
}

@media (any-hover: hover) and (pointer: fine) {
  .c-nav__link:hover {
    color: #0066cc;
  }
}

/* ================================================
   c-fullscreen-nav: フルスクリーンナビゲーション
   ================================================ */
.c-fullscreen-nav {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background-color: var(--fullscreen-bg);

  display: flex;
  align-items: center;
  justify-content: center;

  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--fullscreen-transition),
    visibility var(--fullscreen-transition);
}

.c-fullscreen-nav.is-open {
  opacity: 1;
  visibility: visible;
}

@media (min-width: 768px) {
  .c-fullscreen-nav {
    display: none;
  }
}

/* ================================================
   c-fullscreen-nav__list / item / link: メニューリスト
   ================================================ */
.c-fullscreen-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: center;
}

.c-fullscreen-nav__item {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity var(--fullscreen-transition),
    transform var(--fullscreen-transition);
  transition-delay: 0s;
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item {
  opacity: 1;
  transform: translateY(0);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(1) {
  transition-delay: calc(var(--fullscreen-item-delay) * 1);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(2) {
  transition-delay: calc(var(--fullscreen-item-delay) * 2);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(3) {
  transition-delay: calc(var(--fullscreen-item-delay) * 3);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(4) {
  transition-delay: calc(var(--fullscreen-item-delay) * 4);
}

.c-fullscreen-nav.is-open .c-fullscreen-nav__item:nth-child(5) {
  transition-delay: calc(var(--fullscreen-item-delay) * 5);
}

.c-fullscreen-nav__link {
  display: block;
  padding: 16px 32px;
  color: #fff;
  text-decoration: none;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: 0.1em;
  transition: color 0.2s ease;
}

@media (any-hover: hover) and (pointer: fine) {
  .c-fullscreen-nav__link:hover {
    color: #6eb5ff;
  }
}

JavaScript(assets/js/script.js)

/**
 * 003_hamburger-fullscreen
 * フルスクリーン型ハンバーガーメニュー
 *
 * 機能:
 * - ハンバーガーボタンクリックで画面全体を覆うフルスクリーンメニューを表示
 * - ボタンに is-active、フルスクリーンナビに is-open クラスを付与/除去
 * - aria-expanded / aria-hidden 属性を連動更新(アクセシビリティ対応)
 * - Escape キーで閉じる(フォーカスをハンバーガーボタンに戻す)
 * - スクロールロック(メニュー開放中は body のスクロールを止める)
 * - フォーカストラップ(フルスクリーンナビ内にフォーカスを閉じ込める)
 * - matchMedia でリサイズ時に状態をリセット
 */

(function () {
  "use strict";

  // 要素取得
  const header = document.querySelector(".c-header");
  const hamburger = document.querySelector(".c-hamburger");
  const fullscreenNav = document.querySelector(".c-fullscreen-nav");

  // 要素が存在しない場合は処理しない
  if (!header || !hamburger || !fullscreenNav) return;

  // フォーカス可能な要素のセレクター
  const FOCUSABLE_SELECTOR =
    'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';

  /**
   * メニューを開く
   */
  function openMenu() {
    hamburger.classList.add("is-active");
    fullscreenNav.classList.add("is-open");
    header.classList.add("is-menu-open");

    // aria 属性更新
    hamburger.setAttribute("aria-expanded", "true");
    hamburger.setAttribute("aria-label", "メニューを閉じる");
    fullscreenNav.setAttribute("aria-hidden", "false");

    // スクロールロック
    document.body.style.overflow = "hidden";

    // フルスクリーンナビ内の最初のフォーカス可能要素にフォーカス移動
    const firstFocusable = fullscreenNav.querySelectorAll(FOCUSABLE_SELECTOR)[0];
    if (firstFocusable) {
      setTimeout(function () {
        firstFocusable.focus();
      }, 400);
    }
  }

  /**
   * メニューを閉じる
   */
  function closeMenu() {
    hamburger.classList.remove("is-active");
    fullscreenNav.classList.remove("is-open");
    header.classList.remove("is-menu-open");

    // aria 属性更新
    hamburger.setAttribute("aria-expanded", "false");
    hamburger.setAttribute("aria-label", "メニューを開く");
    fullscreenNav.setAttribute("aria-hidden", "true");

    // スクロールロック解除
    document.body.style.overflow = "";
  }

  /**
   * メニューのトグル
   */
  function toggleMenu() {
    const isOpen = hamburger.classList.contains("is-active");
    if (isOpen) {
      closeMenu();
    } else {
      openMenu();
    }
  }

  /**
   * フォーカストラップ
   * ハンバーガーボタンもトラップ範囲に含める(フルスクリーン上に表示されるため)
   * @param {KeyboardEvent} e
   */
  function trapFocus(e) {
    if (e.key !== "Tab") return;
    if (!fullscreenNav.classList.contains("is-open")) return;

    // ハンバーガーボタン + フルスクリーンナビ内のフォーカス可能要素を結合
    const navFocusable = Array.from(fullscreenNav.querySelectorAll(FOCUSABLE_SELECTOR));
    const focusableElements = [hamburger, ...navFocusable];
    if (focusableElements.length === 0) return;

    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    if (e.shiftKey) {
      if (document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      }
    } else {
      if (document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  }

  // ハンバーガーボタンのクリックイベント
  hamburger.addEventListener("click", toggleMenu);

  // キーボード操作(Escape / Tab)
  document.addEventListener("keydown", function (e) {
    if (e.key === "Escape" && fullscreenNav.classList.contains("is-open")) {
      closeMenu();
      hamburger.focus();
      return;
    }
    if (e.key === "Tab") {
      trapFocus(e);
    }
  });

  // 画面幅が768px以上になったらメニューを閉じる(縦横回転・リサイズ対応)
  const mediaQuery = window.matchMedia("(min-width: 768px)");
  mediaQuery.addEventListener("change", function (e) {
    if (e.matches) {
      if (fullscreenNav.classList.contains("is-open")) {
        closeMenu();
      }
      fullscreenNav.removeAttribute("aria-hidden");
    } else {
      fullscreenNav.setAttribute("aria-hidden", "true");
    }
  });
})();

カスタマイズポイント

コピペしたあと、自分のサイトに合わせて調整したい箇所を紹介します。

  • ブレークポイント(768px): CSSのメディアクエリとJSの matchMedia("(min-width: 768px)") を同じ値に揃えて変更する
  • フルスクリーン背景色: :root--fullscreen-bg を変更する(例: #1a1a1a#2c3e50
  • アニメーション速度: --fullscreen-transition を変更する(例: 0.4s ease0.6s ease
  • リンク出現の間隔: --fullscreen-item-delay を変更する(例: 0.06s0.1s で、より一つずつ感が出る)
  • リンクの色・フォントサイズ: .c-fullscreen-nav__linkcolorfont-size を変更する
  • ヘッダーの高さ: --header-height-sp / --header-height-pc を変更するだけでOK

まとめ

この記事では、CSSとJavaScriptだけで作るフルスクリーンメニューの実装を解説しました。

  • opacity + visibility でフルスクリーンナビをフェード表示し、display: none では実現できないなめらかなアニメーションを作る
  • transition-delaycalc() で段階的に設定することで、リンクが順番にスライドアップする「スタッガードアニメーション」が実現する
  • ヘッダーにz-index上位を与えることで、フルスクリーンの上にロゴと×ボタンが浮かぶデザインを実現する
  • フォーカストラップにハンバーガーボタンを含めることで、フルスクリーン上のすべてのUI要素がキーボードで操作できる

私自身、フルスクリーンメニューを初めて実装したとき、z-indexの階層管理で手こずりました。ヘッダーの×ボタンがメニューの裏に隠れてしまい、閉じるボタンが押せなくなるという初歩的なミスです。z-indexの値をカスタムプロパティにまとめてから「どれが上でどれが下か」が一目で分かるようになり、見通しが一気に良くなりました。

まずはGitHub Pagesのデモで動きを確認して、気に入ったらGitHubリポジトリからコードを持ち帰ってみてください。カスタムプロパティを変えるだけで自分のサイトの雰囲気に合わせられるので、ぜひ試してみてください。


関連記事

【関連記事】CSSとJSだけで作るハンバーガーメニュー【コピペで使えるスニペット付き】

【関連記事】ドロワーメニューの作り方|オーバーレイ・スクロールロック・フォーカストラップまで徹底解説

/

【関連記事】ナビゲーションメニュー実装パターン5選
→ 近日公開予定

【近日公開予定】FLOCSSとは?CSS設計ルールをわかりやすく解説