当サイトは、アフィリエイト広告を利用しています
React + Emotionでボタン押下で画面全体に表示される
モーダル画面を作ってみたので、その実装方法をまとめておく。
この記事ではCSS in JS のEmotionを使うのでもしEmotionの使い方がよくわからない
場合は下記記事で紹介していますので見てみてください!
先にモーダル画面の実装完了したものを載せます
作り方は下記から解説していきます
まずはモーダル表示のみを実装して動作を確認する。
モーダルを表示させる方法としてはCSS in JSのEmotionを使って
動的にCSSを切り替えている。
import "./styles.css";import Modal from "./Modal";import { useState } from "react";import React from 'react';export default function App() {// trueになればmodal表示const [modalFlg, setFlg] = useState(false);return (<><div className="App"><Modal modalFlg={modalFlg} changeModal={setFlg} /><h1>Hello CodeSandbox</h1><h2>Start editing to see some magic happen!</h2><button onClick={() => setFlg((flg) => !flg)}>modal Open!</button></div></>);}
モーダル表示するコンポーネントを作成する。
/** @jsxImportSource @emotion/react */import { css } from "@emotion/react";const Modal = ({ modalFlg, changeModal }) => {return (<div css={[modal(modalFlg)]}><buttoncss={[css`height: 20px;`]}onClick={() => changeModal(flg=>!flg)}>close</button></div>);};export default Modal;const modal = (modalFlg) => {let opacity = 0; //透明ではないlet visibility = "hidden"; //見えないif (modalFlg) {// modalオープン時opacity = 200; //透明度をあげるvisibility = "visible"; //見える}return [css`z-index: 999;/* 定位置 */position: fixed;top: 0;left: 0;/* フルスクリーン */width: 100vw;height: 100vh;background: rgba(0, 0, 0, 0.8);display: flex;justify-content: flex-end;/* 初期状態は非表示 */opacity: ${opacity};visibility: ${visibility};/* ゆっくり表示させる */transition: opacity 0.3s, visibility 0.3s;`];};
emotionでmodalFlgを元にstyleの
の値を設定する。
動作を確認してみる
上記のmodalを使ってmodalでタグを表示し、選択したタグを
呼出し元画面に表示するサンプルを実装してみる
modalの呼出しコンポーネント。 Modalコンポーネントにpropsとして
とそのset関数を渡す。
戻り値のJSXとしては選択確定したタグがある場合は表示する。
import Modal from "./Modal";import { useState } from "react";import React from 'react';export default function App() {// trueになればmodal表示const [modalFlg, setFlg] = useState(false);// 選択確定のタグconst [selectTag, setTag] = useState([]);// Modalに渡すpropsをまとめるconst props = {modalFlg,setFlg,selectTag,setTag};return (<><div style={{textAlign:"center"}}><Modal {...props} /><button onClick={() => setFlg((flg) => !flg)}>modal Open!</button><button onClick={() => setTag([])}>clear</button>{selectTag.length !== 0 ? (selectTag.map((tag) => <div>{tag}</div>)) : (<></>)}</div></>);}
Modalコンポーネント。
選択中のタグを保持するstateを宣言し、modalの表示・非表示切り替えの
タイミングで呼出し元の選択確定タグと一致させるようにする。
/** @jsxImportSource @emotion/react */import { css } from "@emotion/react";import { useState, useEffect } from "react";const Modal = ({ modalFlg, setFlg, selectTag, setTag }) => {// 選択できるタグ一覧const array = Array.of("java","javascript","gatsby","react","css","emotion");// Modalコンポーネントで選択中のタグを保持const [selectingTag, setSeletingTag] = useState([]);//modalFlgが切り替わったタイミング(modal表示/非表示)で// 選択確定タグと選択中タグの選択タグを一致させるuseEffect(() => {setSeletingTag(() => selectTag);}, [modalFlg]);// タグ選択処理const push = (e) => {e.preventDefault();// タグが既に選択中かチェックするconst check = selectingTag.find((tag) => {return tag === e.target.value;});if (!check) {//未選択の場合は追加setSeletingTag((selected) => [...selectingTag, e.target.value]);} else {// 選択済の場合は削除// filterで選択したタグ以外のlistを作り直すsetSeletingTag((selected) =>selectingTag.filter((tag) => tag !== e.target.value));}};// Modalでのタグ選択を確定するconst selectComfilm = () => {// Modalで選択したタグを確定(呼出し元へ反映)setTag(() => selectingTag);// Modalを閉じるsetFlg((flg) => !flg);};// Modalで選択中タグをclearconst clear = () => {// clearsetSeletingTag([]);};return (<div css={[modal(modalFlg)]}><div><div css={[gridContainer]}>{array.map((tag) => (<buttonkey={tag}value={tag}css={[grigItem(selectingTag, tag)]}onClick={(e) => push(e)}>{tag}</button>))}</div><div css={[styles.container]}><buttoncss={[gridItemBase, styles.item]}onClick={() => selectComfilm()}>select</button><button css={[gridItemBase, styles.item]} onClick={() => clear()}>clear</button><buttoncss={[gridItemBase, styles.item]}onClick={() => setFlg((flg) => !flg)}>close</button></div></div></div>);};export default Modal;const modal = (modalFlg) => {let opacity = 0; //透明ではないlet visibility = "hidden"; //不可視化if (modalFlg) {// modalオープン時opacity = 200; //透明度をあげるvisibility = "visible"; //可視化}return [css`/* 一番上に表示 */z-index: 999;/* 定位置 */position: fixed;top: 0;left: 0;/* フルスクリーン */width: 100vw;height: 100vh;background: rgba(0, 0, 0, 0.8);display: flex;justify-content: center;align-items: center;/* 初期状態は非表示 */opacity: ${opacity};visibility: ${visibility};/* ゆっくり表示させる */transition: opacity 0.3s, visibility 0.3s;/* 裏をぼかす */backdrop-filter: blur(1px);`];};// タグ一覧をgridで表示const gridContainer = () => [css`display: grid;grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));gap: 20px 20px;width: 30vw;margin-bottom: 30px;padding: 30px;border-radius: 10px;box-shadow: 0 0.25rem 1.25rem white;`];// タグ選択時のスタイル変更const grigItem = (selectTag, select) => {// 選択したタグが既に選択済かチェックconst count = selectTag.filter((selected) => select === selected);// 選択してない場合はスタイル追加if (count.length !== 0) {return [gridItemBase,css`background-color: darkblue;color: white;`];} else {return [gridItemBase,css`background-color: white;&:hover {background-color: skyblue;}`];}};// タグのスタイルベースconst gridItemBase = () => [css`border: 0px;border-radius: 10px;padding: 5px;transition: All 0.1s 0s ease-in;&:hover {transform: scale(1.1);}&:active {background-color: darkblue;color: white;transform: scale(0.9);}`];// ボタンのスタイルconst styles = {container: () => [css`display: flex;justify-content: space-between;flex-wrap: wrap;gap: 10px;`],item: () => [css`border: 1px solid black;flex: 1;max-width: 100px;text-align: center;`]};
記事の冒頭で載せたものとなります。