はじめに
こちらの方の記事でガラスっぽいデザインをtailwind css
で実装されている方がいらっしゃり、オシャレだ!真似しよう!と思ったので、今回ガラス張りのダイアログをtailwind css
で実装してみました。
実装結果
自分のポートフォリオサイトで下のように実装してみました。
実装方法
ダイアログのテンプレート
ガラスのダイアログをコンポーネント化したコードは以下のようになっています。
先ほど紹介した方の記事からガラスのような見た目になるデザインを引っ張ってきつつ、Framer-motion
でフェードインとフェードアウトを実装しています。
Framer-motion
は別の記事で紹介しているので、よかったらご覧ください。
export const DialogTemplate: React.FC<DialogTemplateProps> = (props) =>{
return (
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-125">
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
exit={{opacity: 0}}
transition={{duration: 1, delay: 0.5}}
className=" backdrop-blur-lg rounded-md border border-gray-200/30 shadow-lg grid grid-cols-2"
>
<Image
src={props.src}
alt="dialog img"
width={500}
height={500}
className="m-0.5"
/>
<div>
{props.children}
</div>
</motion.div>
</div>
)
}
真ん中にダイアログを設置するように、fixed
で画面上にダイアログを固定しつつ、top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2
で真ん中に設定しています。
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-125">
motion.div
でフェードイン、フェードアウトを設定しています。
フェードアウトを実現してるexit
オプションはAnimatePresence
で囲う必要があります。
tailwind css
では、先ほどご紹介した記事のbackdrop-blur-lg
で背景をぼかしたバージョンを使用しています。
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
exit={{opacity: 0}}
transition={{duration: 1, delay: 0.5}}
className=" backdrop-blur-lg rounded-md border border-gray-200/30 shadow-lg grid grid-cols-2"
>
ガラスダイアログコンポーネントを使ってみる
以下のコードに、ガラスコンポーネントの具体的な使い方のコードを書いています。
const BASE_PATH = nextConfig.basePath || "";
export default function WorkPage (){
enum DialogType{
None,
Dialog1,
Dialog2
}
const [dialogType, setDialogType] = React.useState(DialogType.None);
const dialogRef = React.useRef<HTMLDivElement | null>(null);
const handleCloseDialog =(e:MouseEvent) =>{
if((dialogRef.current as any).contains(e.target)) return;
setDialogType(DialogType.None);
}
React.useEffect(()=>{
document.addEventListener("click", handleCloseDialog);
return ()=>{
document.removeEventListener("click", handleCloseDialog);
}
})
return(
<div>
<AnimatePresence>
{dialogType === DialogType.Dialog1 && (
<div className="fixed inset-0 bg-black bg-opacity-50">
<div ref={dialogRef}>
<DialogTemplate
src={`${BASE_PATH}/<publicに置いたパスを書く}`}
>
<div className="mx-2">
<h1 className="flex items-center justify-center underline">Dialog1</h1>
<p>Dialog1の本文</p>
</div>
</DialogTemplate>
</div>
</div>
)}
</AnimatePresence>
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 1, delay: 2}}
onClick={() => setDialogType(DialogType.Report)}
className="my-10 flex flex-col items-center justify-center">
<button>
<Image
src={`${BASE_PATH}/<publicに置いたパスを書く}`}
alt="plateau-sdk"
width={500}
height={400}
/>
</button>
<h1 className="text-2xl my-2 underline">Dialog1</h1>
</motion.div>
</div>
<div className="dialog">
</div>
</div>
)
}
下のコードで、enumで状態(state)を管理しています。enumで複数の状態を列挙しておくことで、どのダイアログが表示されているかを管理することができます。
enum DialogType{
None,
Dialog1,
Dialog2
}
const [dialogType, setDialogType] = React.useState(DialogType.None);
以下のコードでダイアログの外側をタップしたらダイアログを閉じるようにしています。
useRef
を使うことでHTMLのdiv要素をメモ化することが可能で、ダイアログの部分だけを指定します。
そうすることで、その外側をクリックしたらDialogのstateをNone
に変え、ダイアログが表示される前の画面にするようにしています。
const dialogRef = React.useRef<HTMLDivElement | null>(null);
const handleCloseDialog =(e:MouseEvent) =>{
if((dialogRef.current as any).contains(e.target)) return;
setDialogType(DialogType.None);
}
React.useEffect(()=>{
document.addEventListener("click", handleCloseDialog);
return ()=>{
document.removeEventListener("click", handleCloseDialog);
}
})
以下のコードでダイアログのフェードインとフェードアウトを行っています。
dialogRef
をdivタグのref
オプションで指定することで、どの範囲のHTMLDiv
要素をダイアログとするかを指定しています。
{dialogType === DialogType.Dialog1 &&
を記述することで条件付きレンダリングを行うことが可能になります。こうすることでstateの状態によって表示するダイアログを変化させています。
また、DialogTemplate
コンポーネント内でmotion.div
のexit
オプションで指定したフェードアウトを機能させるために、AnimatePresene
でDialogTemplate
を囲うことでフェードアウトを行えるようにしています。
<AnimatePresence>
{dialogType === DialogType.Dialog1 && (
<div className="fixed inset-0 bg-black bg-opacity-50">
<div ref={dialogRef}>
<DialogTemplate
src={`${BASE_PATH}/<publicに置いたパスを書く}`}
>
<div className="mx-2">
<h1 className="flex items-center justify-center underline">Dialog1</h1>
<p>Dialog1の本文</p>
</div>
</DialogTemplate>
</div>
</div>
)}
</AnimatePresence>
最後に
デザインセンスのない自分ですが、Framer-motion
とqiitaの記事を参考にして結構オシャレなダイアログが作れたのではと感じています。
ただ、これはモバイル端末に合わせて作っていないのでいつか調整したいです。