본문 바로가기
개발(Development)/React(리액트)

[React] 모달창(Modal) 쉽고 간단한 구현 방법 (HTML dialog 엘리먼트 사용)

by 카레유 2023. 8. 15.

# 리액트 - 모달창 초간단 구현 방법 

HTML 에서 기본 제공하는 dialog 엘리먼트를 이용하면, 아래의 2가지를 아주 쉽게 구현할 수 있다.

 

1. 모달창

2. 다이얼로그

 

모달창과 다이얼로그의 차이는

- 모달창: dialog 엘리먼트 외부 영역이 불투명하게 dim 처리되고, 스크롤을 제외한 인터랙션(클릭 등)이 허용되지 않는다.

- 다이얼로그: 툴팁처럼 노출되며 인터랙션에 영향을 미치지 않는다.

 

dialog 엘리먼트를 이용하면,

React에서 useState 나 외부 상태 관리 라이브러리 등을 사용하지 않아도 아주 쉽게 모달창을 구현할 수 있다.

당연히 Next.js 13 환경에서도 동일하게 모달창을 구현할 수 있다.


1. 모달창

dialog 엘리먼트는 비노출 상태가 디폴트이며,

(open 속성을 true로 주면 노출 상태가 디폴트가 되긴하지만, 아래의 방법을 추천.)

 

모달창을 노출/비노출시키는 방법은 다음과 같다.

 

showModal()  메서드를 호출하면 모달창이 노출된다. (cf. 다이얼로그는 show() 메서드를 호출하면 노출된다)

close()  메서드를 호출하면 모달창이 사라진다.

 

React에서는 아래와 같이 간단하게 모달창을 구현할 수 있다.

"use client"; // Next.js 13 환경인 경우, 필요에 따라 클라이언트 컴포넌트 사용

import React, { useEffect, useRef } from "react";

export default function Modal() {
  // dialog 참조 ref
  const dialogRef = useRef<HTMLDialogElement>(null);

  // modal 오픈 함수
  const showModal = () => {
    dialogRef.current?.showModal(); // 모달창 노출. show() 호출하면 다이얼로그 노출
  };

  // Modal 닫기 함수
  const closeModal = () => {
    dialogRef.current?.close(); // 모달창 닫기
  };

  return (
    <div>
      {/* 모달 노출 버튼 */}
      <button onClick={showModal}>Open Modal</button>

      {/* dialog 엘리먼트 - 모달창 영역  */}
      <dialog ref={dialogRef}>
        <div>
          {/* 제목 + X버튼 영역 */}
          <span>기본 타이틀</span>
          <button onClick={closeModal}>X버튼</button>
        </div>

        <div>
          {/* 컨텐츠 영역 */}
          <p>기본 컨텐츠가 표시됩니다</p>
        </div>

        <div>
          {/* 확인 버튼 영역 */}
          <button onClick={closeModal}>확인</button>
        </div>
      </dialog>
    </div>
  );
}

 

위의 Modal 컴포넌트를 아래와 같이 사용해주면 된다.

import Modal from "./Modal";

export default function ModalPage() {
  return (
    <div>
      <h1>ModalPage</h1>
      <Modal />
    </div>
  );
}

 

[결과 화면: Open Modal 버튼을 누르면 모달창이 노출된다.]

dialog - 모달창

 

2. 다이얼로그

 show()  메서드를 호출하면, (모달창이 아니라) 다이얼로그가 노출된다.

모달창과 달리 dialog 외부 영역 클릭 등이 가능하다.

 

이 외에는 작동방식과 구현방법 등이 모두 모달창과 동일하다.

dialog - 다이얼로그


끝!

 

 

아주 기본적인 모달창을 구현만 필요하다면, 정말 이걸로 끝이다.

하지만 아래의 3가지 내용이 추가 된다면 훨씬 더 훌륭한 모달창이 될 것이다.

 

1. 배경 dim 처리 영역 스타일링

2. Props로 title, content 등 전달 받기

3. 모달창 외부의 불투명 dim영역 클릭시, 모달창 닫기 구현

 

1. 모달창 배경 dim 처리 영역 스타일링

 ::backdrop  선택자를 사용하면 dim처리영역을 스타일링 할 수 있다.

.dialog::backdrop {
  background-color: rgba(0, 255, 0, 0.4);
}

.dialog {
  width: 50vw;
  background-color: green;
  border: 3px solid blue;
}

 

- 외부 dim 처리 영역을 불투명한 초록색으로,

- 모달창은 노란색, 테두리를 파란색으로 설정해보았다.

 

 

2. Props로 모달창 title, content 등 전달 받기

기본적인 React의 Prop 사용 방식을 사용하면 된다.

"use client";

import React, { useEffect, useRef } from "react";
import styles from "./Modal.module.css";

type Props = {
  title?: string; // 모달창 제목을 Prop으로 받을 수 있다.
  content?: string; // 모달창 컨텐츠를 Prop으로 받을 수 있다.
  onConfirm?: () => void; // 모달창의 확인 버튼 클릭시, 실행할 함수를 Prop으로 받을 수 있다.
};

export default function Modal({ title, content, onConfirm }: Props) {
  // dialog 참조 ref
  const dialogRef = useRef<HTMLDialogElement | null>(null);

  // modal 오픈 함수
  const showModal = () => {
    dialogRef.current?.showModal(); // 모달창 노출. show() 호출하면 다이얼로그 노출
  };

  // Modal 닫기 함수
  const closeModal = () => {
    dialogRef.current?.close(); // 모달창 닫기
  };

  return (
    <div>
      {/* 모달 노출 버튼 */}
      <button onClick={showModal}>Open Modal</button>

      {/* dialog 엘리먼트 - 모달창 영역  */}
      <dialog ref={dialogRef} className={styles.dialog}>
        <div>
          {/* 제목 + X버튼 영역 */}
          <span>{title || "기본 타이틀"}</span>
          <button onClick={closeModal}>X버튼</button>
        </div>

        <div>
          {/* 컨텐츠 영역 */}
          <p>{content || "기본 컨텐츠가 표시됩니다."}</p>
        </div>

        <div>
          {/* 확인 버튼 영역 */}
          <button onClick={onConfirm || closeModal}>확인</button>
        </div>
      </dialog>
    </div>
  );
}

 

Modal 컴포넌트를 부르는 부모 컴포넌트에서는 아래와 같이 사용해주면 된다.

"use client";
import Modal from "./Modal";

export default function ModalPage() {
  return (
    <div>
      <h1>ModalPage</h1>
      <Modal
        title="부모의 명령"
        content="내가 내려주는 컨텐츠를 노출하도록 하라"
        onConfirm={() => {
          alert("onConfirm이 호출되었어요~");
        }}
      />
    </div>
  );
}

 

[결과 화면: 모달창의 "확인" 버튼을 누르면 팝업이 노출된다.]

 

3. 모달창 외부의 불투명 dim영역 클릭시, 모달창 닫기 구현

모달창 외부의 영역이 클릭되면, 모달창을 닫도록 이벤트 리스너를 장착해주면 된다.

React 환경이므로, useEffect에서 정의 해주었다.

"use client";

import React, { useEffect, useRef } from "react";
import styles from "./Modal.module.css";

type Props = {
  title?: string; // 모달창 제목을 Prop으로 받을 수 있다.
  content?: string; // 모달창 컨텐츠를 Prop으로 받을 수 있다.
  onConfirm?: () => void; // 모달창의 확인 버튼 클릭시, 실행할 함수를 Prop으로 받을 수 있다.
};

export default function Modal({ title, content, onConfirm }: Props) {
  // dialog 참조 ref
  const dialogRef = useRef<HTMLDialogElement | null>(null);

  // modal 오픈 함수
  const showModal = () => {
    dialogRef.current?.showModal(); // 모달창 노출. show() 호출하면 다이얼로그 노출
  };

  // Modal 닫기 함수
  const closeModal = () => {
    dialogRef.current?.close(); // 모달창 닫기
  };

  useEffect(() => {
    // 모달창 외부영역 클릭시, 모달창 닫기 구현
    if (dialogRef.current) {
      const dialogElement = dialogRef.current as HTMLDialogElement;

      dialogElement.addEventListener("click", (event) => {
        const dialogArea = dialogElement.getBoundingClientRect();
        if (
          event.clientX < dialogArea.left ||
          event.clientX > dialogArea.right ||
          event.clientY < dialogArea.top ||
          event.clientY > dialogArea.bottom
        ) {
          dialogElement.close();
        }
      });
    }
  }, []);

  return (
    <div>
      {/* 모달 노출 버튼 */}
      <button onClick={showModal}>Open Modal</button>

      {/* dialog 엘리먼트 - 모달창 영역  */}
      <dialog ref={dialogRef} className={styles.dialog}>
        <div>
          {/* 제목 + X버튼 영역 */}
          <span>{title || "기본 타이틀"}</span>
          <button onClick={closeModal}>X버튼</button>
        </div>

        <div>
          {/* 컨텐츠 영역 */}
          <p>{content || "기본 컨텐츠가 표시됩니다."}</p>
        </div>

        <div>
          {/* 확인 버튼 영역 */}
          <button onClick={onConfirm || closeModal}>확인</button>
        </div>
      </dialog>
    </div>
  );
}

 

각자의 개발 구조에 따라, 이벤트 리스너 해제 등의 세부 구현은 상황에 맞게 진행 하시면 좋을듯 합니다.


HTML dialog 엘리먼트를 사용하지 않고,

수동으로 모달창을 구현하려면, 아래 글을 참고하시기 바랍니다.

 

[React] 모달창(Modal) 초간단 구현 방법(리덕스, 라이브러리 X)

 

 

댓글