dialogタグを知っていますか? Reactでdialogタグを使ったDialog・Modal Componentの作成方法
こんにちは、AIQ株式会社のフロントエンドエンジニアのまさぴょんです!
今回は、Reactでdialogタグを使ったDialog・Modal Componentの作成方法について解説していきます。
dialogタグを知っていますか?
あなたは、dialog
タグを知っていますか?
今まで、Dialog・Modalなどを実装するときは、MUIなどのUIライブラリを使用することが多かったのですが。。。
今回、新規プロジェクトで、MUIを使わない状態で、ゼロイチでDialog・Modal Componentを作成する際に何かいいものはないかと調べていたら、dialog
タグを発見しました。
dialogタグの使い方
それでは、React で、dialog
タグを使った Dialog Componentを作成する前に、dialog
タグの使い方について解説していきます。
dialog
タグの使い方について、すでにご存知の方は、ここのSectionは、スキップしてください。
dialog
タグの使い方は、すでに個人Blogの方にまとめていますが、改めて、まとめたものをこちらにも掲載しておきます。
dialogタグのブラウザ対応状況
まず、一番重要な各ブラウザのdialog
タグの対応状況は次のとおりです。
各ブラウザで、対応済みなので、問題なく使用できることで一安心です。
dialogタグの使い方と特徴
dialog
タグを使用する上で押さえておきたい使い方と特徴は、次のとおりです。
dialogタグの使い方と特徴
-
dialog
要素にはopen
属性をつけない場合は何も表示されないです。- 実態は、
open
属性の有無によるdisplay
属性のnone
かblock
かのSwitchになります。 - つまり、
dialog
要素にopen
属性を追加することで、Dialogが表示されるので、動的なopen
属性の追加が必要になります。
- 実態は、
-
HTMLDialogElement.show()
またはHTMLDialogElement.showModal()
を使用することで、open
属性を追加して、Dialogを表示することができます。-
HTMLDialogElement.show()
は、open
属性を追加するだけのメソッドです。 -
HTMLDialogElement.showModal()
は、open
属性の他にtop-layer
とbackdrop
の属性も追加するメソッドです。-
top-layer
は、z-index
を設定しなくても一番上の Layer として表示される属性です。 -
backdrop
は、Dialogの背景色を設定するための属性です。
-
-
-
HTMLDialogElement.close()
を使用することで、open
属性を削除して、Dialogを非表示にすることができます。
それでは、上記の特徴を理解した上で、SampleCodeとその実行結果を見ていきます。
dialogタグで作るDialogのSampleCodeと実行結果
今回のDialogのSampleCodeを実行すると、初期画面は、次のような Openボタンが表示されているだけです。
そして、これをClickすると、次のような Dialogが中央に表示されます。
上記のSampleCodeは、次のとおりです。
要点には、Numberと解説を掲載しています。
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dialog-Test</title>
</head>
<style>
/*
1. 背景のスクロールの停止
=> ページ内のコンテンツが多くスクロールが可能なページで、Dialog を表示すると、
=> Dialog が中央に表示されていても背景のスクロールを行うことができます。
=> Dialog が表示された状態で背景のスクロールを停止するために 次のようなStyleを追加します。
*/
html:has(dialogアコーディオンにtitleを入力してください) {
overflow: hidden;
}
/* 2. dialog の背景は backdrop で設定されているので backdrop に背景色を設定することで指定した色に変わります。 */
dialog::backdrop {
background-color: skyblue;
/* background-color: rgb(250, 250, 250, 0.5); */
}
/* 3. 「padding: 0」を追加で、Dialog内部の paddingでのClick判定を無効化する */
.custom-dialog {
padding: 0;
width: 500px;
border: 1 solid black;
border-radius: 8px;
}
</style>
<body>
<div>
<button class="openBtn">Open</button>
<!-- 4. dialogタグ -->
<dialog class="custom-dialog">
<!--
5. divタグ
=> style を設定することで dialog の領域一杯にクリック領域を調整しています
=> dialog 要素の中に div 要素を追加することで、追加した div 要素内であればクリックしたとしても Dialog を非表示にさせないように設定を行います。
-->
<div style="padding: 1em">
<h2>Dialog</h2>
<button class="closeBtn">Close</button>
</div>
</dialog>
<script>
// 6. Dialog & openBtn & closeBtn Element
const dialog = document.querySelector("dialog");
const openBtn = document.querySelector(".openBtn");
const closeBtn = document.querySelector(".closeBtn");
openBtn.addEventListener("click", () => {
// 7. HTMLDialogElement.showModal()
// => open属性追加 & top-layer & backdrop の追加
// => top-layer は z-index を設定しなくても一番上の Layer として表示されるので、
// => Dialog が表示された場合に下の Layer にアクセスすることができせん。
// => backdrop は背景色を設定するために利用することができます。デフォルトでは薄いグレーが設定されています。
dialog.showModal();
});
closeBtn.addEventListener("click", () => {
// 8. HTMLDialogElement.close() を使用することで、open属性を削除して、Dialogを非表示にすることができます。
dialog.close();
});
dialog.addEventListener("close", (e) => {
// 9. close イベント
// => Dialog を非表示にする場合に close イベントを設定することで Dialog が非表示になったことを検知することができます。
console.log("Dialog が Close されました");
});
/**
* 10. Dialog の外側のクリック
* => Dialog の外側にある背景をクリックすることで Dialog を非表示にする場合の設定を確認していきます。
* => モーダルウィンドウでは背景をオーバーレイと呼ぶことがありますが、オーバーレイをクリックした場合に表示されている Dialog を非表示にします。
*/
dialog.addEventListener("click", (event) => {
console.log("Dialog_Click");
console.log(event.target);
// ポイント1: 外側の領域も含めた DialogElementを Clickしていたら、Closeする
// ポイント2: dialogタグ内部の divタグより内側は、対象外にする
if (event.target === dialog) {
dialog.close();
}
});
</script>
</div>
</body>
</html>
Dialogの領域展開・Dialogの外側のクリックと、内側のクリックの判定制御について
Dialog の外側のクリックと、内側のクリックの判定制御について、Logicは、SrcCodeに記載してある解説のとおりになります。
ポイントをまとめると、次のとおりです。
Dialogの領域展開・Dialogの外側のクリックと、内側のクリックの判定制御Logicまとめ
- Dialogは、
HTMLDialogElement.showModal()
で表示しているので、top-layer
に領域展開している。- 周りの背景部分も、Dialogの領域。
-
dialog
タグにpadding: 0
を追加で、Dialog内部のpadding
でのClick判定を無効化する。 -
dialog
タグ直下のdiv
タグをstyle設定で、Dialog内部すべてを満たすようにする。 -
dialog
タグのclick
イベントの際に、dialog
タグかdialog
以外かで、判定する。
Reactでdialogタグを使ったDialog・Modal Componentの作成方法
dialog
タグの理解ができたところで、本題の Reactでdialogタグを使ったDialog・Modal Componentの作成方法に移ります。
作成するDialog Componentのイメージ
まずは、作成するDialog Componentのイメージを共有します。
作成するのは、次のような DialogをOpenするためのボタンと、
次のような Dialogになります。
dialogタグを使ったDialog ComponentのSampleCode
Dialog ComponentのSampleCodeは、次のとおりです。
Dialog Componentでの処理の要点をまとめると、次のとおりです。
Dialog Componentを呼ぶための処理の要点
-
useRef()
で、dialogタグを参照する。 -
useEffect()
を使って、isOpen
の変更を検知して、開閉処理を制御する。 -
showModal()
で、Dialogを開くので、top-layer
でDialogを領域展開する。 -
dialog
と、Dialog内部のdiv
のStyleで、展開した領域の範囲を制御する。 - Dialogの内側は、CloseBtn以外では、閉じられないように
event.stopPropagation();
で、EventをCancelする。
import { CSSProperties, useCallback, useEffect, useRef } from "react";
/** Propsの型定義 */
interface PropsType {
/** 開閉を管理する Flag */
isOpen: boolean;
/** Dialogを閉じるためのFunction */
onClose?: VoidFunction;
dialogStyle?: CSSProperties;
dialogInnerStyle?: CSSProperties;
}
/**
* NOTE: CustomDialog
* => dialogタグを使って、作成した Custom 可能な Dialog Component
*/
const CustomDialog = (props: PropsType) => {
const { onClose, isOpen, dialogStyle, dialogInnerStyle } = props;
/**
* Dialog_Element
* => useRef() で、dialogタグを参照する
*/
const dialogRef = useRef<HTMLDialogElement>(null);
/** isOpen の変更を検知して動作する */
useEffect((): void => {
const dialogElement = dialogRef.current;
if (!dialogElement) {
return;
}
// 1. Dialog Open Flag === true
if (isOpen) {
// 1-2. 属性値: open が Set されていたら、処理終了
if (dialogElement.hasAttribute("open")) {
return;
}
// 1-3. showModal()で、Dialogを表示する
// => top-layer と backdrop の設定も自動で追加される
dialogElement.showModal();
} else {
// 2. Dialog Open Flag === false
// 2-1. 属性値: open が Set されていなかったら、処理終了
if (!dialogElement.hasAttribute("open")) {
return;
}
// 2-2. close()で、Dialogを閉じる
dialogElement.close();
}
}, [isOpen]);
/**
* Dialog を Close する Func
* => Closeボタン or Dialog の外部領域を Click すると Call
*/
const onCloseDialog = useCallback((): void => {
onClose?.();
}, [onClose]);
/** Dialog の 内部領域の Close Event を Cancel する */
const handleClickContent = useCallback(
(event: React.MouseEvent<HTMLDivElement>): void => {
// clickイベントの伝搬を止める。
event.stopPropagation();
console.log("Dialog Click");
},
[]
);
/** Dialog の DefaultStyle */
const defaultDialogStyle = {
padding: "0" /** PaddingもDialog外部領域と判定されるので、0にする */,
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
border: "1px solid black",
borderRadius: "8px",
} as CSSProperties;
/** Dialogの Inner Div の Style */
const defaultDialogInnerStyle = {
width: "500px",
height: "500px",
padding: "1em",
textAlign: "center",
display: "flex",
flexDirection: "column",
justifyContent: "space-evenly",
alignItems: "center",
} as CSSProperties;
return (
<dialog
ref={dialogRef}
onClick={() => {
onCloseDialog();
}}
style={dialogStyle ? dialogStyle : defaultDialogStyle}
>
{/* Dialog Inner 領域 => 内側は、CloseBtn 以外では、閉じられないように EventCancel する */}
<div
onClick={(e) => {
handleClickContent(e);
}}
style={dialogInnerStyle ? dialogInnerStyle : defaultDialogInnerStyle}
>
<h2>Dialog_Test</h2>
<button
onClick={() => onCloseDialog()}
style={{ width: "60px", height: "30px" }}
>
Close
</button>
</div>
</dialog>
);
};
export default CustomDialog;
上記のDialog Componentを親Componentでは、次のように使います。
親Componentでの処理の要点をまとめると、次のとおりです。
親Componentで、Dialog Componentを呼ぶための処理の要点
- Dialog の開閉を管理する Flagを用意する
- Openのボタンと、Dialogを開くOpenFuncを用意する。
- Dialogを閉じるCloseFuncを用意して、Dialog Component に CallBackFuncとして渡す。
import { useState } from "react";
import CustomDialog from "./atoms/dialog/CustomDialog";
const Home = () => {
/** Dialog の開閉を管理する Flag */
const [isOpen, setIsOpen] = useState(false);
/** Dialog を開く Func */
const openDialog = () => {
setIsOpen(true);
};
/** Dialog を閉じる Func */
const closeDialog = () => {
setIsOpen(false);
};
return (
<div>
{/* Dialogを Openする Btn */}
<div style={{ textAlign: "center" }}>
<button onClick={() => openDialog()}>Dialog Open</button>
</div>
{/* Dialog_Component */}
<CustomDialog isOpen={isOpen} onClose={closeDialog} />
</div>
);
};
export default Home;
まとめ
dialog
タグを活用することで、シンプルにDialogを作成できることがわかりました。
また、Reactでの実装でも簡単に、Customize可能なDialogを作成することが可能だとわかったので、活用していきたいと思います。
[参考・引用]
注意事項
この記事は、AIQ 株式会社の社員による個人の見解であり、所属する組織の公式見解ではありません。
求む、冒険者!
AIQ株式会社では、一緒に働いてくれるエンジニアを絶賛、募集しております🐱🐹✨
エンジニア視点での我が社のおすすめポイント
- フルリモート・フルフレックスの働きやすい環境!
- 前の会社でアサインしてた現場は、フル出社だったので、ありがたすぎる。。。
- もうフル出社には、戻れなくなります!
- 経験豊富なエンジニアの先輩方
- 私は、3年目の駆け出しエンジニアなので、これが、かなりありがたいです!
- 自社開発とR&D(受託開発)を両方している会社なので、経験できる技術が多い。
- 自社のProduct開発と、他社からの受託案件で、いろいろな技術を学ぶことができます。
- AI関連の最新の技術に触れられるチャンスが多い。
- 自社で特許を持つほど、AI技術に強い会社で、プロファイリングを得意とした技術体系があります。
- ChatGPTを自社アプリに搭載など、AIトレンドも、もちろん追っており、最新の技術に触れられるチャンスが多いです。
- たまに、札幌ラボ(東京から札幌) or 東京オフィス(札幌から東京)に出張で行ける!
- 東京と、札幌に2拠点ある会社なので、会合などで集まる際に、出張で行けます。
採用技術 (一部抜粋)
- FrontEnd: TypeScript, JavaScript, React.js, Vue.js, Next.js, Nuxt.js など
- BackEnd: Node.js, Express,Python など
- その他技術: Docker, AWS, Git, GitHub など
エントリー方法
- 私達と東京か札幌で一緒に働ける仲間を募集しています。
詳しくは、Wantedly (https://www.wantedly.com/companies/aiqlab)を見てみてください。
Webエンジニア向け説明
データサイエンティスト向け説明
人事に直通(?)・ご紹介Plan(リファラル採用)
私経由で、ご紹介もできますので、興味のある方や気軽にどんな会社なのか知りたい方は、X(旧:Twitter)にて、DMを送ってくれても大丈夫です。
AIQ 株式会社 に所属するエンジニアが技術情報をお届けします。 ※ AIQ 株式会社 社員による個人の見解であり、所属する組織の公式見解ではありません。 Wantedly: wantedly.com/companies/aiqlab
Discussion