Reactで自作ポップアップを作る【Tailwind】

React+Tailwindで自作ポップアップ
◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
◆ 次回のライブ配信について ◆
●マイクロソフトが提供するRAGアーキテクチャの詳解とデモを行うイベントを開催します。以下のリンクからご参加ください。

⇒ 2/27(火)19:00〜 RAG構築のためのAzure OpenAI Serviceリファレンスアーキテクチャ詳解 https://tech-lab.connpass.com/event/307678/

初めに

どもども新米フロントエンドエンジニアの龍ちゃんです。今回は、お客さんの要望でポップアップ表示(モーダルウィンドウ)の実装が飛んできたので記事にまとめています。環境としては「React」と「Tailwind」になります。コンポーネントを全部自作していたので、なかなか激しい作業量になっているのはご愛嬌ということで…コンポーネント分割って難しいですね。

今回のブログで解決する問題と要望は以下になる予定です。

  • React + Tailwind環境で自作モーダルウィンドウ
  • モーダルウィンドウ表示に後ろの要素がスクロールされる
  • スクロールを固定したら後ろの画面が動く(スクロールバーのwidth計算)
  • モーダルウィンドウの枠外をクリックしたら画面が閉じる

それでは初めて行きます。

とりあえず完成形とコード

とりあえず今回目標にしている形を先にお見せします。

環境とセットでほしい方は僕の検証用のリポジトリからCloneしてください。検証環境はViteで作成しています。こちらからCloneして以下コマンドで起動します。

yarn install
yarn dev

それではソースの紹介をしていきます。モーダルウィンドウを作成するコンポーネントのコードはこちらです。適当に「PopUpComponent.tsx」というファイルを作成して保存してください。

import React, { memo, useEffect } from 'react';

import { RxCross1 } from 'react-icons/rx';

type Props = {
  viewFlag: boolean;
  setViewFlag: React.Dispatch<React.SetStateAction<boolean>>;
};

export const PopUpComponent = memo((props: Props) => {
  const { viewFlag, setViewFlag } = props;
  useEffect(() => {
    // 背景画面固定用関数
    const registerBackgroundFixed = () => {
      const body = document.body;
      const scrollWidth = window.innerWidth - body.clientWidth;
      body.style.marginRight = `${scrollWidth}px`;
      body.style.overflowY = 'hidden';
    };
    // 背景画面固定解除用関数
    const unRegisterBackgroundFixed = () => {
      const body = document.body;
      body.style.overflowY = '';
      body.style.marginRight = '';
    };
    if (viewFlag) registerBackgroundFixed();

    return () => {
      unRegisterBackgroundFixed();
    };
  }, [viewFlag]);

  // 枠外クリック用関数
  const onClickBackground = () => {
    setViewFlag(false);
  };

  // 枠内クリック
  const onClickCard = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    e.stopPropagation();
  };

  return (
    <>
      <div
        className={
          'fixed flex flex-col items-center justify-center overflow-hidden bg-gray-500/50 transition-all ' +
          (viewFlag
            ? ' top-0 left-0 h-screen w-screen '
            : ' top-1/2 left-1/2 h-0 w-0 ')
        }
        onClick={onClickBackground}
      >
        <div className="relative h-3/4 w-3/4 max-w-3xl">
          {/* バツボタン */}
          <div className="absolute right-0 -top-10 h-10 w-10 hover:cursor-pointer">
            <RxCross1 className="h-full w-full" />
          </div>
          <div
            id="policy"
            className="flex h-full w-full flex-col bg-white"
            onClick={onClickCard}
          >
            ここに何かしらを書く
          </div>
        </div>
      </div>
    </>
  );
}); 

使用方法としては以下のようになります。useStateで管理するためのboolean変数を用意してください。そちらを「TRUE:表示」・「FALSE:非表示」といった具合に使用します。

import { useState } from 'react';

import { PopUpComponent } from '../modules/PopupComponent';

export const PopupPage = () => {
  const [popupFlag, setPopupFlag] = useState<boolean>(false);
  return (
    <section className="h-[2000px] w-full">
      <button onClick={() => setPopupFlag(true)}>popup</button>
      <PopUpComponent viewFlag={popupFlag} setViewFlag={setPopupFlag} />
    </section>
  );
}; 

詳しい内容については以下の順序で解説を入れていきます。

  • モーダルウィンドウ(ポップアップ)表示の作り方
  • 枠外クリックイベントの補足方法
  • 背面スクロールの防止(スクロールバーが消えた際の画面のちらつき対処)

モーダルウィンドウ表示の作り方【Tailwind】

ここではTailwindの記法についての紹介になります。今回作成する要素のワイヤーフレーム的なものを一応出しておきます。

Tailwindのデザインワイヤーフレーム

構成要素としては、以下の三つになります。

  • 画面を覆う大きさで下の要素を透過する灰色
  • バツ印アイコン
  • 「何かを表示するところ」の白色

また、モーダルウィンドウなので非表示状態の表現も必要になります。CSSとしての切り分けとしては以下の想定をしています

  • 表示時:画面をすべて覆うように表示「height:100%」「width:100%」
  • 非表示時:要素を表示しない「height:0px」「width:0px」

それではコードです。

<div
        className={
          'fixed flex flex-col items-center justify-center overflow-hidden bg-gray-500/50 transition-all ' +
          (viewFlag
            ? ' top-0 left-0 h-screen w-screen '
            : ' top-1/2 left-1/2 h-0 w-0 ')
        }
        onClick={onClickBackground}
      >
        <div className="relative h-3/4 w-3/4 max-w-3xl">
          {/* バツボタン */}
          <div className="absolute right-0 -top-10 h-10 w-10 hover:cursor-pointer">
            <RxCross1 className="h-full w-full" />
          </div>
          <div
            id="policy"
            className="flex h-full w-full flex-col bg-white"
            onClick={onClickCard}
          >
            ここに何かしらを書く
          </div>
        </div>
      </div> 

内容としては、灰色を「fixed」で定義して画面を覆います。中心に「div」要素を配置してその中で「バツ印」と「白色」を配置しています。

「ViewFlag」の値によって適応するCSSを振り分けています。三項演算子で切り替えを行なっています。

枠外イベントのクリック

こちらはReactというよりは、JavaScriptの機能をほとんど使っていて、ちょっとTypescriptの恩恵を受けようとしている内容になります。

まず先にイメージを掴むために図を出します。

ポップアップの挙動についての説明

バツ印を押して閉じるのは当然ですが、枠外をクリックして閉じる設計の方が親切ですよね。モーダルウィンドウをどこまで作り込むかという話になりますが、自分があって欲しいものはユーザにとっても欲しいものであると仮定して行きましょう。

今回のデザイン構成で留意すべき点があります。親要素に「onClick」を追加すると、小要素をクリックした際にもイベントが発火します。下のサンプルの青と白の部分を交互にクリックしてみてください。

See the Pen 背景のイベント伝搬例 by Ryu Tana (@ReiRyuSTI) on CodePen.

白枠をクリックした際に、青枠に設定している関数も動作しました。これは、青枠の上に白枠が載っているのでイベントが伝搬しました。それを防止するために「stopPropagation」を使用します。使用して重複を避ける場合がこちらになります。

See the Pen 背景のイベント伝搬防止 by Ryu Tana (@ReiRyuSTI) on CodePen.

こちらのサンプルでは、先ほど白枠がクリックした際に親要素の関数が走っていたのを防止することができています。

それではポップアップの枠外をクリックして閉じるための関数の解説を始めていきます。以下がその部分のコードになります。

// 枠外クリック用関数
const onClickBackground = () => {
  setViewFlag(false);
};

// 枠内クリック
const onClickCard = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
  e.stopPropagation();
};

「onClickBackground 」が枠外クリック、「onClickCard」が枠内のクリックになります。枠内クリックの場合では、「stopPropagation」を使用してイベント伝搬を止めています。クロス部分をクリックした場合では、イベント伝搬が行われて「onClickBackground」関数が処理されます。

こちらは、HTML上で適応する関数を入れ替えて遊んでみると良いかもしれません。

背面スクロールの防止(スクロールバーが消えた際の画面のちらつき対処)

先ほどのサンプル紹介で、ポップアップの表示はできるようになりました。ですがポップアップを表示する画面がスクロール可能な場合、要素を貫通させてスクロールすることが可能です。それだと他のエンジニアさんからツッコミが入りそうなので、ポップアップ表示中は後ろの要素を固定するようにしましょう。

以下がソースになります。こちらはReactの機能であるuseEffectを使用して作成しています。

useEffect(() => {
    // 固定用関数
    const registerBackgroundFixed = () => {
      const body = document.body;
      const scrollWidth = window.innerWidth - body.clientWidth;
      body.style.marginRight = `${scrollWidth}px`;
      body.style.overflowY = 'hidden';
    };

    // 画面固定解除用関数
    const unRegisterBackgroundFixed = () => {
      const body = document.body;
      body.style.overflowY = '';
      body.style.marginRight = '';
    };

    if (viewFlag) registerBackgroundFixed();
    return () => {
      unRegisterBackgroundFixed();
    };
  }, [viewFlag]);

viewFlagは描画を司る変数です。この変数が切り替わるタイミングで背景の固定の登録を行なっています。クリーンアップ処理として背景の固定を解除するプログラムも実行しています。

またちょっとしたこだわりですが、スクロールバーの幅を計算してmarginとして右側に登録しています。こちらの処理の理由としては、背景の固定を行なった際にスクロールバーが消えるためデザインがちらつくのを防止するためです。

終わりに

お疲れ様です。今回はReact+Tailwind環境でポップアップ(モーダルウィンドウ)を自作する方法について解説しました。表示の際のアニメーションなど趣向を凝らすことができる要素はありますが、基本の形ができたのであとはじっくりとデザインをつけていこうと思います。

本来なら、UIライブラリなどを使えば楽なのですが、自作することで得れる発見もあるので、作業時間と睨めっこして自作に取り組んでいこうと思います。

ありがとうございました!

もし興味があればこちらもご一読ください。

それではまた

アバター画像
About 龍:Ryu 104 Articles
2022年入社で主にフロントエンドの業務でTailwindと遊ぶ日々。お酒とうまいご飯が好きで、運動がちょっと嫌いなエンジニアです。しゃべれるエンジニアを目指しておしゃべりとブログ執筆に注力中(業務もね)//
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

1人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる