3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

tailwind cssでガラスっぽいダイアログを実装してみる[Next.js TailwindCSS]

Last updated at Posted at 2024-08-05

はじめに

こちらの方の記事でガラスっぽいデザインをtailwind cssで実装されている方がいらっしゃり、オシャレだ!真似しよう!と思ったので、今回ガラス張りのダイアログをtailwind cssで実装してみました。

実装結果

自分のポートフォリオサイトで下のように実装してみました。

ダイアログ.gif

実装方法

ダイアログのテンプレート

ガラスのダイアログをコンポーネント化したコードは以下のようになっています。
先ほど紹介した方の記事からガラスのような見た目になるデザインを引っ張ってきつつ、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.divexitオプションで指定したフェードアウトを機能させるために、AnimatePreseneDialogTemplateを囲うことでフェードアウトを行えるようにしています。

 <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の記事を参考にして結構オシャレなダイアログが作れたのではと感じています。
ただ、これはモバイル端末に合わせて作っていないのでいつか調整したいです。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?