コハム

Webクリエイターのコハムが提供する、Web制作に役立つ情報をまとめたブログです。

CSSだけ!Javascriptなしで実装するモーダル徹底解説

What is a modal? And how to build a CSS-only Modal

記事は上記記事を意訳したものです。

※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。


CSSは基本的なスタイリングというつつましい始まりから、大きく進化してきました。擬似クラスの使用により、要素の特定の振る舞いに基づいてスタイリングができるようになりました。このチュートリアルでは、:target 擬似クラスを使用してモーダル要素を構築する方法を説明します。

モーダルとは?

モーダルは、Webページの他の要素の上に表示される要素で、モーダルが閉じられるまでWebページの他の部分との対話を防ぎます。モーダルは通常、ユーザーに関連する情報を表示したり、必要なアクションを促したりするために使用されます。

モーダルはユーザーの操作によって表示・非表示が切り替わるため、一般的にJavaScriptを使用して構築されます。JavaScriptコードを書かなくてもモーダルを扱えるビルトインのJavaScript APIも存在します。

しかし、このチュートリアルでは、JavaScriptを一切使用せずにモーダルを構築する方法を説明します。代わりに、CSSのみで機能するモーダルコンポーネントを作成します。

:target CSS擬似クラスは、URLのフラグメントと一致するIDを持つ固有の要素(ターゲット要素)を表します。- MDN

モーダルの機能構築

以下の機能を持つモーダルを作成します:

  • ボタンをクリックすると開く
  • モーダル外の領域またはクローズボタンをクリックすると閉じる

そのために、以下の要素が必要です:

  • モーダルを開くための要素
  • モーダルコンテンツを保持するモーダルコンテナ要素
  • モーダル要素外のクリックを処理するモーダルオーバーレイ
  • モーダル要素を閉じるためのクローズボタン

モーダルを開く

モーダルを開く処理を実装するために、アンカー要素を使用してURLフラグメントにモーダルのIDを設定します。アンカー要素は、#と要素IDを使用してWebページの特定のセクションにリンクすることができます。

<!-- 別のウェブサイトへのリンク -->
<a href="https://tutsplus.com/authors/jemima-abu"></a>
<!-- 同じWebページの対応するIDを持つセクションへのリンク -->
<a href="#tutorials"></a>

アンカータグでURLフラグメントを設定すると、ページURLは次のようになります: https://tutsplus.com/authors/jemima-abu#tutorials

フラグメントを使用すると、ウィンドウの位置履歴も変更されます。つまり、ユーザーが https://tutsplus.com/authors/jemima-abu#tutorials にいて、ブラウザの戻るボタンを押すと、同じWebページの https://tutsplus.com/authors/jemima-abu にリダイレクトされます。

CSS :target 擬似クラスは、このフラグメントを使用して一致するIDを持つ要素にスタイルを適用します。モーダルのIDがURLフラグメントとして設定されると、:target を使用してモーダル要素を表示できます。

モーダルを閉じる

同じ理由で、URLフラグメントのIDを別のものに変更すると、:target スタイリングはモーダルに適用されなくなります。オーバーレイとクローズボタンの要素をアンカータグとして設定し、URLフラグメントをリセットすることで、モーダルから:targetスタイリングを削除し、非表示にすることができます。

モーダルを閉じるには、以下のいずれかの方法でオーバーレイとクローズボタンのアンカー要素のhrefを設定できます:

  • 空のフラグメント(例:href="#")を設定
  • 別のセクションIDを設定
  • 存在しないID(#blank)を使用

空のフラグメントを使用するメリット: - URLに不要なテキストが含まれない - デメリット:モーダルが閉じられたときにページが上部にスクロールする

別のセクションIDを使用するメリット: - モーダルセクションが閉じられたときにフォーカスを当てる要素を決定できる - デメリット:要素が表示領域にスクロールされるため、わずかなページのジャンプが発生する可能性がある

存在しないIDを使用するメリット: - モーダルが閉じられたときにページのスクロール位置が維持される - デメリット:一部のブラウザではフラグメントテキストがURLに表示され、違和感がある場合がある

モーダルのHTML構造

モーダルのHTMLレイアウトは以下のようになります:

<main>
  <a class="modal-btn" href="#modal">モーダルを開く</a>
</main>
<section role="dialog" class="modal" id="modal" aria-labelledby="modal-title">
  <a class="modal-overlay" href="#" tabindex="-1"></a>
  <div class="modal-content">
    <a title="モーダルを閉じる" aria-label="モーダルを閉じる" href="#" class="modal-close">&times;</a>
    <h2 id="modal-title">こんにちは!</h2>
  </div>
</section>

モーダルのアクセシブルなラベリングのために以下の属性を使用しています:

  • role="dialog": 支援技術に要素がモーダルであることを伝える
  • aria-labelledby="modal-title": 'modal-title'要素のテキストを使用してモーダルコンテナにタイトルを提供
  • aria-label="モーダルを閉じる": モーダルを閉じるボタンに説明的なテキストを提供
  • tabindex="-1": キーボード使用時にモーダルオーバーレイがフォーカスを得ることを防ぐ(オーバーレイは主に視覚的な手がかりとして機能し、必須の機能ではないため)

モーダルのスタイリング

レイアウトが設定できたら、アンカータグとモーダル要素に基本的なスタイリングを適用できます。アンカータグをボタンのように見せ、モーダル要素を他の要素の上に配置し、モーダルオーバーレイには半透明の背景を設定します。

.modal-btn {
  transition: background 250ms;
  padding: 16px 24px;
  border-radius: 4px;
  background-color: #0f0f0f;
  color: #fcfcfc;
  text-transform: uppercase;
  font-size: 12px;
  letter-spacing: 0.1em;
  margin-top: 32px;
  display: inline-block;
  text-decoration: none;
}

.modal-btn:hover,
.modal-btn:focus {
  background-color: #0f0f0fdd;
}

.modal {
  position: fixed;
  min-height: 100vh;
  width: 100%;
  top: 0;
  left: 0;
  display: flex;
  z-index: 2;
}

.modal-overlay {
  width: 100%;
  height: 100%;
  position: absolute;
  background-color: rgba(0, 0, 0, 0.5);
  left: 0;
}

.modal-content {
  transition: transform 1s;
  background: #fff;
  width: 75%;
  position: relative;
  margin: auto;
  height: 75%;
  padding: 48px 24px;
  border-radius: 4px;
  max-width: 1000px;
}

.modal-close {
  font-size: 36px;
  text-decoration: none;
  color: inherit;
  position: absolute;
  right: 24px;
  top: 10px;
}

モーダルはデフォルトで表示されるようにスタイリングされているので、:not擬似クラスを使用してターゲット要素でない場合にモーダルを非表示にする必要があります:

.modal:not(:target) {
  display: none;
}

モーダルのアニメーション

CSSを使用しているので、よりスムーズなモーダルの表示・非表示のためにトランジションとアニメーションを適用することもできます。以下は、フェードとスライドアニメーションを適用したCSSコードです:

.modal:not(:target) {
  visibility: hidden;
  transition-delay: 500ms;
  transition-property: visibility;
}

.modal:target .modal-content {
  transform: translateY(100vh);
  animation: 500ms ease-in-out slideUp forwards;
}

.modal:not(:target) .modal-content {
  transform: translateY(0);
  animation: 500ms ease-out slideDown forwards;
}

.modal:target .modal-overlay {
  opacity: 0;
  animation: 500ms linear fadeIn forwards;
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes slideUp {
  from {
    transform: translateY(100vh);
  }
  to {
    transform: translateY(0);
  }
}

@keyframes slideDown {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(100vh);
  }
}

終了アニメーションが完了してから完全に非表示になるように、モーダルのvisibilityトランジションを遅延させます。

モーダルのアクセシビリティ

モーダル要素を構築する際には、以下のアクセシビリティ要件を考慮する必要があります:

  • モーダルが開いたときにフォーカスを得て、閉じたときにメインページにフォーカスを戻す
  • モーダルコンテンツは開いているときのみスクリーンリーダーに認識される
  • キーボードでモーダルを閉じることができる

URLフラグメントの仕組みのおかげで、アンカータグがクリックされると自動的にモーダルにフォーカスが移動します。これは、URLフラグメントが一致するIDを持つセクションまでスクロールし、それによってフォーカスも移動するためです。

メインページへのフォーカス移動に関しては、クローズボタンとオーバーレイのhref="#"属性を使用すると、フォーカスはメインページに戻ります。

また、要素のIDを使用することで、モーダルボタンなど特定の要素にフォーカスを戻すこともできます。この方法は、ユーザーがモーダルを開く前と同じフォーカス状態を維持できるため、推奨されます:

<a id="modal-btn" class="modal-btn" href="#modal">モーダルを開く</a>
<section role="dialog" class="modal" id="modal" aria-labelledby="modal-title">
  <a class="modal-overlay" href="#modal-btn"></a>
  <div class="modal-content">
    <a title="モーダルを閉じる" aria-label="モーダルを閉じる" href="#modal-btn" class="modal-close">&times;</a>
    <h2 id="modal-title">こんにちは!</h2>
  </div>
</section>

JavaScriptの補助が必要

残りの機能については、ネイティブの動作を利用できないため、アクセシビリティ要件を処理するためにJavaScriptを使用する必要があります。

モーダルのaria-hidden属性を切り替え、Escapeキーが押されたときにモーダルを閉じることができるようにイベントリスナーを追加するスクリプトを作成します。

URLフラグメントを使用してモーダルの表示を切り替えているので、JavaScriptでもpopstateイベントリスナーを使用して同じ方法を使用できます。このイベントリスナーはウィンドウのURLが変更されたことを検知します。そしてwindow.location.hash値を使用してURLフラグメントの値を取得できます:

const modal = document.getElementById('modal')
window.addEventListener('popstate', () => {
  if (window.location.hash === '#modal') {
    modal.focus();
    modal.setAttribute('aria-hidden', false);
    return
  }
  
  modal.setAttribute('aria-hidden', true)
});

また、keydownイベントリスナーを使用してescapeキーが押されたかどうかとモーダルが開いているかどうかを検知します。その場合、window.location.hash値を空にしてモーダルを閉じます:

window.addEventListener('keydown', (e) => {
  if (e.key == "Escape" && window.location.hash === '#modal') {
    window.location.hash = ""
  }
})

これで、CSSを使用した機能的なモーダルコンポーネント(アクセシビリティのための若干のJavaこれで、CSSを使用した機能的なモーダルコンポーネント(アクセシビリティのためのJavaScriptも含む)の構築が完了しました。

©コハム