投稿日:2023年4月17日

引き続きReactを検証したいと思います。
今回はモーダルウィンドウを実装します。

モーダルウィンドウとは、クリックすると表示され、表示されている間は他の動作ができなくなる子ウィンドウのことを言います。

今回作ったものです。

▼モーダルコンポーネントのソースコードです。

// ▼▼▼ モーダル
function Images() {
  // 表示させる関数
  const openModal = (e) => {
    let pageYOffset = window.pageYOffset;  // 縦方向のスクロール量→モーダル画像と閉じるボタンのY座標を取得
    let targetThumbnail = e.target;//ターゲットに設定
    targetThumbnail.nextElementSibling.style.display = 'block';//サムネール画像の次の弟要素(overlayWrap)を表示
    targetThumbnail.nextElementSibling.setAttribute('id', 'overlayWrap');//弟要素(overlayWrap)にid値付与
    document.getElementById('overlayWrap').children[1].setAttribute('id', 'closeBtn');//閉じるボタンにid値付与
    document.getElementById('overlayWrap').children[2].setAttribute('id', 'largeImg');//モーダル画像にid値付与
    let closeBtn = document.getElementById('closeBtn');//閉じるボタンのid値取得
    let largeImg = document.getElementById('largeImg');//モーダル画像のid値取得
    closeBtn.style.top = pageYOffset+10+"px";//閉じるボタンのY座標を設定
    largeImg.style.top = pageYOffset+10+"px";//モーダル画像のY座標を設定
  }

  //非表示にする関数
  const closeModal = () => {
    let overlayWrap = document.getElementById('overlayWrap');//overlayWrap取得
    let closeBtn = document.getElementById('closeBtn');//閉じるボタン取得
    let largeImg = document.getElementById('largeImg');//モーダル画像取得
    overlayWrap.style.display = 'none';//非表示に
    overlayWrap.style.top = null;//Y座標をリセット
    overlayWrap.removeAttribute("id");//overlayWrapのid削除
    closeBtn.removeAttribute("id");//閉じるボタンのid削除
    largeImg.removeAttribute("id");//モーダル画像のid削除
  }

  // ゼロパッディング
  const zeroPad = (num) => {return ('00' + num).slice(-2);}

  return (
    <>
      {(() => {
        const items = [];//配列を準備
        for (let i = 1; i <= 12; i++){//12回繰り返す
          items.push( //配列に追加
            <figure key={i}>
              {/* サムネール画像 */}
              <img src={`${process.env.PUBLIC_URL}/images/${zeroPad(i)}_s.webp`} onClick={openModal} className="thumbnailImg"/>
              {/* モーダル画像とグレー背景、閉じるボタン*/}
              <div className="overlayWrap" onClick={closeModal}>
                <div className="overlay"></div>
                <p className="closeBtn">×</p>
                <img src={`${process.env.PUBLIC_URL}/images/${zeroPad(i)}_l.webp`}  className="largeImg" />
              </div>
            </figure>
          )
        }
        return items;
      })()}
    </>
  )
}

export default Images;

コメントにも書きましたが要点を説明します。

let targetThumbnail = e.target;

▲クリック対象を変数 targetThumbnail に格納します。
各サムネール画像をクリックすると直下の要素(弟要素)を表示します。

targetThumbnail.nextElementSibling.style.display = 'block';
targetThumbnail.nextElementSibling.setAttribute('id', 'overlayWrap');

▲7行目。nextElementSibling を使用し、サムネール画像の直下に記述したモーダルを display:block で表示させます。
8行目。表示させたモーダルにid値 overlayWrap を付与します。

{(() => {
  const items = [];//配列を準備
    for (let i = 1; i <= 12; i++){//12回繰り返す
      items.push( //配列に追加
        <figure key={i}>
          {/* サムネール画像 */}
          <img src={`${process.env.PUBLIC_URL}/images/${zeroPad(i)}_s.webp`} onClick={openModal} className="thumbnailImg"/>
          {/* モーダル画像とグレー背景、閉じるボタン*/}
          <div className="overlayWrap" onClick={closeModal}>
            <div className="overlay"></div>
            <p className="closeBtn">×</p>
            <img src={`${process.env.PUBLIC_URL}/images/${zeroPad(i)}_l.webp`}  className="largeImg" />
          </div>
        </figure>
      )
  }
  return items;
})()}

▲34〜51行目、JSXです。
JSX内にif文を書くため即時関数を使用しました。
今回は12枚の画像を使用するので12回繰り返しサムネール画像とモーダルを出力します。
この書き方の問題点は、モーダルの背景と閉じるボタンも各画像分出力されてしまうことです。
即時関数の外に記述したり検証しましたが、少々冗長になりますが、閉じる工程を鑑み、この記述がベターかと思いました。。。
38行目。<figure key={i}> のように書かないとエラーが出ます。
${process.env.PUBLIC_URL} は public 内のディレクトリを参照する記述になります。

<div className="overlayWrap" onClick={closeModal}>
  <div className="overlay"></div>
  <p className="closeBtn">×</p>
  <img src={`${process.env.PUBLIC_URL}/images/${zeroPad(i)}_l.webp`}  className="largeImg" />
</div>

▲42〜46行目。グレー背景(overlay)と閉じるボタンです。
ページ読み込み時はCSSで非表示にしておき、クリックで表示させます。
表示すると共にid値を付与し、閉じときに取得し非表示にする仕様にしています。
▼表示後のタグ。赤囲みが付与したid。

まとめ

状態の切り替えのためuseStateや、Reactのモーダルライブラリreact-modal などの使用も検証してみました。
が、今回のようなモーダルまでカスタマイズできなかったので自作してみました。

jQueryには以前の記事 で紹介したようなキレイに快適に動作するライブラリもあります。

今回は、ほぼほぼバニラJSのみの記述になってしまいReactらしさを感じないのが否めません。。。
次回以降、stateも含めてさらに検証していきたいと思います。
最後まで読んでくださりありがとうございました。

関連サイト
Pocket