Reactでモーダル実装|基本的なポップアップUIの作り方

Reactでモーダル(ポップアップ)を実装する基本的な方法をコード例付きで解説。useState、useEffect、Portalの使い方も学べます。

Learning Next 運営
37 分で読めます

みなさん、こんなことありませんか?

「Reactでモーダル実装に挑戦したいけど、どうすればいいの?」 「ポップアップを作りたいけど、複雑で分からない」 「他のサイトみたいな、きれいなモーダルを作りたい」

Webアプリケーションを開発していると、ユーザーに確認を求めたり、詳細情報を表示したりするために、モーダル(ポップアップ)が必要になることが多くあります。 でも、初心者にとってモーダルの実装は意外と難しく感じられるものですよね。

大丈夫です! この記事では、Reactでモーダルを実装する基本的な方法から、実践的なカスタマイズまで、コード例を交えて詳しく解説します。

一緒に、使いやすいモーダルUIを作っていきましょう!

モーダルって何?

モーダルは、メインコンテンツの上に表示される「ダイアログボックス」のことです。 ユーザーがモーダルを閉じるまで、背景のコンテンツは操作できなくなります。

モーダルの基本的な特徴

モーダルには以下のような特徴があります。

  • オーバーレイ表示: 既存のコンテンツの上に重ねて表示
  • 背景のブロック: モーダルが開いている間は背景の操作を無効化
  • 明確な閉じる手段: ×ボタンや背景クリックで閉じられる
  • フォーカス管理: モーダル内に適切にフォーカスを移動

簡単に言うと、ユーザーの注意を特定のコンテンツに集中させるためのUIコンポーネントです。 よく見るのは、確認ダイアログや画像の拡大表示ですね。

基本的なモーダルを作ってみよう

まず、最もシンプルなモーダルを実装してみましょう。

基本的なモーダルコンポーネント

以下のコードは、基本的なモーダルの実装例です。

import React, { useState } from 'react';
import './Modal.css';

function Modal({ isOpen, onClose, title, children }) {
  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button className="modal-close" onClick={onClose}>
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>
  );
}

export default Modal;

このコードの流れを詳しく説明しますね。

if (!isOpen) return nullで、モーダルが閉じている時は何も表示しません。 これで、表示・非表示の制御ができます。

modal-overlayは、画面全体を覆う半透明の背景です。 クリックするとonCloseが実行されて、モーダルが閉じます。

modal-contentは、実際のモーダルの内容です。 onClick={(e) => e.stopPropagation()}で、モーダル内をクリックしても閉じないようにしています。

ヘッダー部分には、タイトルと閉じるボタン(×)があります。 ボディ部分には、childrenで渡されたコンテンツが表示されます。

モーダルを使うコンポーネント

次に、作成したモーダルを実際に使ってみましょう。

import React, { useState } from 'react';
import Modal from './Modal';

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const openModal = () => setIsModalOpen(true);
  const closeModal = () => setIsModalOpen(false);

  return (
    <div>
      <h1>モーダル実装サンプル</h1>
      <button onClick={openModal}>モーダルを開く</button>
      
      <Modal
        isOpen={isModalOpen}
        onClose={closeModal}
        title="サンプルモーダル"
      >
        <p>これはモーダルの内容です。</p>
        <p>任意のコンテンツを表示できます。</p>
      </Modal>
    </div>
  );
}

export default App;

使い方はとてもシンプルですね。

useStateisModalOpenの状態を管理しています。 trueの時にモーダルが表示され、falseの時に非表示になります。

openModalcloseModalの関数で、状態を切り替えます。 ボタンをクリックするとopenModalが実行されて、モーダルが表示されます。

Modalコンポーネントに、必要なpropsを渡しています。 childrenとして渡したコンテンツが、モーダル内に表示されます。

CSSスタイリングの基本

モーダルを美しく表示するためには、適切なCSSスタイリングが必要です。

基本的なモーダルスタイル

モーダルのスタイリングの基本的な考え方を見てみましょう。

/* モーダルのオーバーレイ */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

/* モーダルのコンテンツ */
.modal-content {
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  max-width: 500px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
}

/* モーダルのヘッダー */
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px;
  border-bottom: 1px solid #e0e0e0;
}

.modal-close {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #666;
}

.modal-close:hover {
  color: #000;
}

/* モーダルのボディ */
.modal-body {
  padding: 20px;
}

このCSSのポイントを説明しますね。

modal-overlayでは、position: fixedで画面全体を覆います。 background-color: rgba(0, 0, 0, 0.5)で、半透明の黒い背景を作ります。

Flexboxを使って、モーダルを画面の中央に配置しています。 z-index: 1000で、他の要素より前面に表示されるようにしています。

modal-contentでは、白い背景に角丸とシャドウを付けて、モーダルらしい見た目にしています。 max-height: 80vhで、画面の80%以下の高さに制限しています。

modal-headerでは、Flexboxでタイトルと閉じるボタンを両端に配置しています。 下に境界線を引いて、ヘッダーとボディを区別しています。

modal-closeでは、×ボタンのスタイルを設定しています。 ホバー時に色が変わることで、クリック可能だと分かりやすくしています。

React Portalを使った実装

より適切なモーダル実装には、React Portalを使用することをおすすめします。

Portal を使う理由

React Portalを使うことで、以下のメリットがあります。

  • DOMの構造を最適化: モーダルを適切な位置に配置
  • CSSの影響を回避: 親要素のスタイルに影響されない
  • z-indexの問題を回避: より確実に最前面に表示

通常のReactコンポーネントは、親要素の中にレンダリングされます。 でも、モーダルは画面全体を覆うので、bodyの直下に配置する方が自然なんです。

Portal を使ったモーダル実装

React Portalを使った実装例を見てみましょう。

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children }) {
  const [modalRoot, setModalRoot] = useState(null);

  useEffect(() => {
    // モーダル用のDOM要素を作成
    const root = document.createElement('div');
    root.id = 'modal-root';
    document.body.appendChild(root);
    setModalRoot(root);

    return () => {
      // クリーンアップ処理
      document.body.removeChild(root);
    };
  }, []);

  if (!isOpen || !modalRoot) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button className="modal-close" onClick={onClose}>
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    modalRoot
  );
}

export default Modal;

Portalの仕組みを詳しく説明しますね。

useEffectで、modal-rootというIDのdiv要素を作成しています。 この要素をdocument.bodyに追加して、モーダル専用の場所を確保します。

createPortalで、モーダルのコンテンツをその場所にレンダリングします。 第1引数がレンダリングする内容、第2引数が配置先の要素です。

クリーンアップ処理で、作成した要素を削除しています。 これで、メモリリークを防げます。

if (!isOpen || !modalRoot) return nullで、モーダルが閉じている時や、まだ配置先が準備できていない時は何も表示しません。

このようにPortalを使用することで、より柔軟で安全なモーダル実装が可能になります。

キーボードイベントの処理

ユーザビリティを向上させるため、キーボードイベントの処理も実装しましょう。

Escキーでモーダルを閉じる

Escキーでモーダルを閉じる機能を追加してみましょう。

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children }) {
  const [modalRoot, setModalRoot] = useState(null);

  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        onClose();
      }
    };

    if (isOpen) {
      document.addEventListener('keydown', handleKeyDown);
    }

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen, onClose]);

  useEffect(() => {
    const root = document.createElement('div');
    root.id = 'modal-root';
    document.body.appendChild(root);
    setModalRoot(root);

    return () => {
      document.body.removeChild(root);
    };
  }, []);

  if (!isOpen || !modalRoot) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button className="modal-close" onClick={onClose}>
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    modalRoot
  );
}

export default Modal;

キーボードイベント処理の仕組みを説明しますね。

handleKeyDown関数で、押されたキーをチェックしています。 event.key === 'Escape'で、Escキーが押されたかどうかを判定します。

Escキーが押されたら、onClose()を実行してモーダルを閉じます。

document.addEventListenerで、キーボードイベントリスナーを追加しています。 isOpentrueの時だけ、イベントリスナーを有効にします。

クリーンアップ処理で、イベントリスナーを削除しています。 これで、メモリリークや意図しないイベント処理を防げます。

これにより、ユーザーがEscキーを押すことでモーダルを閉じることができます。 キーボード操作に慣れたユーザーには、とても便利な機能ですね。

フォーカス管理の実装

アクセシビリティを向上させるため、フォーカス管理も実装しましょう。

フォーカストラップの実装

モーダル内でフォーカスを適切に管理するためのコードです。

import React, { useState, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children }) {
  const [modalRoot, setModalRoot] = useState(null);
  const modalRef = useRef(null);
  const previousFocusRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // モーダルが開いた時の処理
      previousFocusRef.current = document.activeElement;
      
      // モーダル内の最初のフォーカス可能な要素にフォーカス
      setTimeout(() => {
        if (modalRef.current) {
          const focusableElements = modalRef.current.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          );
          if (focusableElements.length > 0) {
            focusableElements[0].focus();
          }
        }
      }, 100);
    }

    return () => {
      // モーダルが閉じた時に元の要素にフォーカスを戻す
      if (previousFocusRef.current) {
        previousFocusRef.current.focus();
      }
    };
  }, [isOpen]);

  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        onClose();
      }
    };

    if (isOpen) {
      document.addEventListener('keydown', handleKeyDown);
    }

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen, onClose]);

  useEffect(() => {
    const root = document.createElement('div');
    root.id = 'modal-root';
    document.body.appendChild(root);
    setModalRoot(root);

    return () => {
      document.body.removeChild(root);
    };
  }, []);

  if (!isOpen || !modalRoot) return null;

  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div 
        className="modal-content" 
        onClick={(e) => e.stopPropagation()}
        ref={modalRef}
      >
        <div className="modal-header">
          <h2>{title}</h2>
          <button className="modal-close" onClick={onClose}>
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    modalRoot
  );
}

export default Modal;

フォーカス管理の仕組みを詳しく説明しますね。

modalRefで、モーダルのコンテンツ要素への参照を保持しています。 previousFocusRefで、モーダルを開く前にフォーカスがあった要素を記憶しています。

モーダルが開いた時の処理を見てみましょう。 まず、document.activeElementで現在フォーカスがある要素を保存します。

次に、モーダル内のフォーカス可能な要素を検索します。 ボタン、リンク、入力フィールドなどが対象です。

見つかった最初の要素にfocus()でフォーカスを移動します。 setTimeoutを使って、少し遅延させているのは、DOM更新を待つためです。

モーダルが閉じた時の処理では、保存しておいた元の要素にフォーカスを戻しています。 これで、キーボード操作がスムーズに続けられます。

このようなフォーカス管理により、モーダルのアクセシビリティが大幅に向上します。 特に、スクリーンリーダーを使うユーザーや、キーボードで操作するユーザーに優しい設計になります。

アニメーションの追加

モーダルにアニメーションを追加して、より滑らかな表示を実現しましょう。

CSS トランジションを使ったアニメーション

まず、CSSアニメーションを追加します。

/* アニメーション付きモーダル */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.modal-overlay.modal-open {
  opacity: 1;
}

.modal-content {
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  max-width: 500px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
  transform: scale(0.9);
  transition: transform 0.3s ease;
}

.modal-overlay.modal-open .modal-content {
  transform: scale(1);
}

このCSSアニメーションの仕組みを説明しますね。

オーバーレイのフェードイン: opacity: 0からopacity: 1への変化で、背景がゆっくりと現れます。 transition: opacity 0.3s easeで、0.3秒かけて滑らかに変化させています。

コンテンツのスケールアニメーション: transform: scale(0.9)からtransform: scale(1)への変化で、モーダルが少し小さい状態から通常サイズに拡大します。 これで、「ふわっと現れる」感じの演出ができます。

.modal-openクラスが付いた時に、アニメーションが開始されます。 JavaScriptで、このクラスの付け外しを制御します。

アニメーション対応のモーダルコンポーネント

アニメーションに対応したモーダルの実装です。

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children }) {
  const [modalRoot, setModalRoot] = useState(null);
  const [isAnimating, setIsAnimating] = useState(false);

  useEffect(() => {
    if (isOpen) {
      setIsAnimating(true);
      // アニメーション開始のため少し遅延
      setTimeout(() => setIsAnimating(false), 10);
    }
  }, [isOpen]);

  useEffect(() => {
    const root = document.createElement('div');
    root.id = 'modal-root';
    document.body.appendChild(root);
    setModalRoot(root);

    return () => {
      document.body.removeChild(root);
    };
  }, []);

  if (!isOpen || !modalRoot) return null;

  return createPortal(
    <div 
      className={`modal-overlay ${!isAnimating ? 'modal-open' : ''}`}
      onClick={onClose}
    >
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>{title}</h2>
          <button className="modal-close" onClick={onClose}>
            ×
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    modalRoot
  );
}

export default Modal;

アニメーション制御の仕組みを説明しますね。

isAnimatingという状態で、アニメーションの開始タイミングを制御しています。 モーダルが開いた時、最初はisAnimatingtrueになります。

その後、setTimeoutで10ミリ秒後にisAnimatingfalseにします。 これで、少し遅延してからアニメーションが開始されます。

className={modal-overlay ${!isAnimating ? 'modal-open' : ''}}で、条件に応じてクラスを付けています。 isAnimatingfalseになると、modal-openクラスが追加されて、CSSアニメーションが開始されます。

このように段階的にクラスを制御することで、滑らかなアニメーションを実現できます。

このようにアニメーションを追加することで、より洗練されたユーザー体験を提供できます。 ユーザーにとって、モーダルの開閉がより自然に感じられるようになりますね。

実用的なモーダルのバリエーション

実際のプロジェクトでは、用途に応じて様々なモーダルが必要になります。

確認ダイアログ

ユーザーに確認を求めるモーダルの実装例です。

function ConfirmModal({ isOpen, onClose, onConfirm, message }) {
  const handleConfirm = () => {
    onConfirm();
    onClose();
  };

  return (
    <Modal isOpen={isOpen} onClose={onClose} title="確認">
      <p>{message}</p>
      <div className="modal-buttons">
        <button onClick={onClose} className="btn-cancel">
          キャンセル
        </button>
        <button onClick={handleConfirm} className="btn-confirm">
          確認
        </button>
      </div>
    </Modal>
  );
}

確認ダイアログの仕組みを説明しますね。

messageプロップで、表示するメッセージを受け取ります。 例えば、「本当に削除しますか?」などのテキストです。

onConfirmプロップで、確認ボタンが押された時の処理を受け取ります。 handleConfirm関数では、まずonConfirm()を実行してから、onClose()でモーダルを閉じます。

ボタンは2つあります。 「キャンセル」ボタンは、何もせずにモーダルを閉じるだけです。 「確認」ボタンは、処理を実行してからモーダルを閉じます。

フォーム入力モーダル

フォーム要素を含むモーダルの実装例です。

function FormModal({ isOpen, onClose, onSubmit }) {
  const [formData, setFormData] = useState({
    name: '',
    email: ''
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(formData);
    onClose();
  };

  return (
    <Modal isOpen={isOpen} onClose={onClose} title="情報入力">
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label>名前:</label>
          <input
            type="text"
            value={formData.name}
            onChange={(e) => setFormData({...formData, name: e.target.value})}
            required
          />
        </div>
        <div className="form-group">
          <label>メールアドレス:</label>
          <input
            type="email"
            value={formData.email}
            onChange={(e) => setFormData({...formData, email: e.target.value})}
            required
          />
        </div>
        <div className="modal-buttons">
          <button type="button" onClick={onClose} className="btn-cancel">
            キャンセル
          </button>
          <button type="submit" className="btn-submit">
            送信
          </button>
        </div>
      </form>
    </Modal>
  );
}

フォームモーダルの仕組みを説明しますね。

formDataという状態で、入力値を管理しています。 オブジェクト形式で、複数の入力項目をまとめて管理できます。

onChangeイベントで、入力値の変更を監視しています。 setFormData({...formData, name: e.target.value})で、スプレッド演算子を使って既存のデータを保持しつつ、特定のフィールドだけを更新します。

handleSubmit関数では、e.preventDefault()でデフォルトのフォーム送信を防いでいます。 その後、onSubmit(formData)で入力データを親コンポーネントに渡してから、モーダルを閉じます。

送信ボタンはtype="submit"にして、Enterキーでも送信できるようにしています。 キャンセルボタンはtype="button"にして、フォーム送信を防いでいます。

このように、用途に応じてモーダルをカスタマイズすることができます。 確認ダイアログやフォーム入力など、様々な場面で活用できますね。

モーダル実装のベストプラクティス

モーダルを実装する際に気をつけるべきポイントをまとめました。

パフォーマンス最適化

モーダルのパフォーマンスを最適化するためのポイントです。

  • 条件付きレンダリング: 必要な時だけモーダルをレンダリング
  • メモ化: React.memoを使用して不要な再レンダリングを防止
  • 遅延読み込み: 重いコンテンツは必要になってから読み込み

条件付きレンダリングは、特に重要です。 モーダルが閉じている時は、DOM要素を作らないようにしましょう。

メモ化を使うことで、親コンポーネントが再レンダリングされても、モーダルは不要な更新を避けられます。

アクセシビリティ対応

アクセシビリティを考慮した実装のポイントです。

  • 適切なARIAラベル: スクリーンリーダーへの対応
  • フォーカス管理: キーボードナビゲーションの最適化
  • 色のコントラスト: 視覚的にわかりやすい色使い

ARIAラベルを付けることで、スクリーンリーダーを使うユーザーにも内容が伝わりやすくなります。 例えば、aria-label="モーダルを閉じる"をボタンに付けるなどです。

ユーザビリティ向上

ユーザビリティを向上させるためのポイントです。

  • 明確な閉じる手段: 複数の方法でモーダルを閉じられる
  • 適切なサイズ: コンテンツに応じた適切なサイズ設定
  • レスポンシブ対応: 様々なデバイスサイズに対応

モーダルを閉じる方法は、複数用意しておくと親切です。 ×ボタン、背景クリック、Escキーなど、ユーザーの好みに応じて選べるようにしましょう。

心配いりません、これらのポイントを意識して実装すれば、使いやすいモーダルを作成できます。

よくある問題と解決方法

モーダル実装でよく遭遇する問題と解決方法をまとめました。

z-indexの問題

問題: モーダルが他の要素に隠れてしまう

解決方法:

.modal-overlay {
  z-index: 9999; /* 十分に高い値を設定 */
}

z-indexの値は、サイト全体で使われている値を確認して設定しましょう。 通常、9999のような高い値を設定すれば、他の要素より前面に表示されます。

スクロール問題

問題: モーダルが開いている時に背景がスクロールできてしまう

解決方法:

useEffect(() => {
  if (isOpen) {
    document.body.style.overflow = 'hidden';
  } else {
    document.body.style.overflow = 'unset';
  }
  
  return () => {
    document.body.style.overflow = 'unset';
  };
}, [isOpen]);

この処理で、モーダルが開いている間は背景のスクロールを無効化できます。 overflow: 'hidden'でスクロールを無効にし、overflow: 'unset'で元に戻します。

クリーンアップ処理も忘れずに追加しましょう。 これで、モーダルが意図せず閉じた場合でも、スクロールが復活します。

モバイル対応

問題: モバイルデバイスでモーダルが適切に表示されない

解決方法:

@media (max-width: 768px) {
  .modal-content {
    width: 95%;
    max-height: 90vh;
    margin: 20px;
  }
}

モバイルでは、画面サイズに応じてモーダルのサイズを調整しましょう。 幅は95%程度にして、画面の端に少し余白を残します。

高さも90vh程度に制限して、画面からはみ出さないようにします。 マージンを追加することで、画面の端にぴったりくっつかないようにしています。

これらの対策により、より安定したモーダル実装が可能になります。 問題が起きた時は、これらの解決方法を試してみてくださいね。

まとめ:使いやすいモーダルを作ろう

Reactでのモーダル実装について、基本的な方法から実践的なテクニックまで解説しました。

重要なポイント

  • useState: モーダルの開閉状態を管理
  • Portal: 適切なDOM構造でモーダルを表示
  • イベント処理: キーボードイベントとクリックイベントの適切な処理
  • アクセシビリティ: フォーカス管理とARIAラベルの実装
  • スタイリング: 視覚的に美しく使いやすいデザイン

これらの要素を組み合わせることで、プロレベルのモーダルが作れます。

実装の段階

モーダル実装は以下の段階で進めることをおすすめします。

  1. 基本的なモーダル: 最小限の機能で動作確認
  2. スタイリング: CSSで見た目を整える
  3. Portal対応: より適切なDOM構造に変更
  4. アクセシビリティ: キーボード操作とフォーカス管理
  5. アニメーション: 滑らかな表示・非表示効果
  6. カスタマイズ: 用途に応じた機能追加

最初から完璧を目指さず、段階的に機能を追加していくのがコツです。 まずは動くものを作って、少しずつ改善していきましょう。

今日から始めよう

モーダルは多くのWebアプリケーションで必要になる重要なUIコンポーネントです。 基本的な実装から始めて、段階的に機能を追加していくことで、使いやすいモーダルを作成できます。

ぜひ、実際のプロジェクトでモーダル実装にチャレンジしてみてください。 きっと、「こんなに簡単にモーダルが作れるんだ!」と驚くはずです。

あなたも今日から、素敵なモーダルを作ってみませんか?

関連記事