[Next.js] 이미지가 포함된 모달 띄우기

최익준·2023년 8월 25일
0

Next.js

목록 보기
3/7

이미지가 포함된 모달 띄우기

Next.js Image를 사용하면 처음 이미지를 불러오는 코드를 작성할 때부터 이미지의 크기를 지정해주어야 한다.
따라서 화면 크기에 따라 이미지의 사이즈를 동적으로 바꿀 수 없다는 문제가 발생한다.

문제1: 모달에 이미지 띄우기

생각한 방법은 아래 두 가지 방법이다.

  • 첫번 째: 사이즈를 지정해주는 대신 그 이미지를 감싸고 있는 div의 크기를 동적으로 변화시키고 그 div안에 이미지가 채워지는 방식으로 진행한다. 하지만 그 div의 크기에 이미지를 맞추었을 때 가로 또는 세로 중 짧은 부분에 사이즈가 맞춰져버린다는 문제가 생긴다. (해본 결과 모달을 닫는 과정에서 조금 걸리는 점이 있음)
  • 두번 째: 컴포넌트를 렌더링 할 때 화면의 너비와 높이를 불러오고, 그 너비와 높이의 상태에 따라 이미지의 width와 height를 설정해준다. -> useEffect 안에 window.innerHeight와 같은 메소드를 사용해서 화면 크기를 불러오고, 그 크기에 맞춰 이미지의 크기를 설정.

나는 두번 째 방법을 사용하여 이 문제를 해결했다.

  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const [margin, setMargin] = useState(0);

  useEffect(() => {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    if (windowWidth > windowHeight) {
      setHeight(windowHeight * 0.8);
      setWidth((windowHeight * 0.8) / 1.414);
      setMargin(windowHeight * 0.1);
    } else {
      setWidth(windowWidth * 0.8);
      setHeight(windowWidth * 0.8 * 1.414);
      setMargin((windowHeight - windowWidth * 0.8 * 1.414) / 2);
    }
  }, []);

내가 띄우려는 모든 사진의 비율은 1:sqrt(2) 이다. 따라서 그냥 1.414를 곱하거나 나누어주는 식으로 너비와 높이를 계산해버렸다. 또한 화면 가운데에 모달 이미지가 위치하도록 만들기 위해서 margin-top을 위 코드와 같이 새로운 변수에 할당하여 만들어주었다.

그런데 여기서 문제는 useEffect로 화면이 처음 렌더링될 때 높이와 너비를 한 번만 불러오는 방식이기 때문에 사용자가 새로고침 없이 화면의 크기만 바꾼다면 이미지의 비율이 맞지 않게 된다.

문제2: Image를 반응형으로 만들기

앞서 언급한 문제를 해결하고자 다음 방법을 고안했다.
window.addEventListener('resize', callback);을 사용하고 useEffect에서 이 값이 변할때마다 가로 세로 계산이 실행되도록 하는 것이다.

주의: removeEventListener 무조건 해줘야함!!

작성한 코드는 다음과 같다.

const modalSizing = () => {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    if (windowWidth > windowHeight) {
      setHeight(windowHeight * 0.8);
      setWidth((windowHeight * 0.8) / 1.414);
      setMargin(windowHeight * 0.1);
    } else {
      setWidth(windowWidth * 0.8);
      setHeight(windowWidth * 0.8 * 1.414);
      setMargin((windowHeight - windowWidth * 0.8 * 1.414) / 2);
    }
  };

  useEffect(() => {
		modalSizing();
    window.addEventListener("resize", modalSizing);
    return () => {
      window.removeEventListener("resize", modalSizing);
    };
  }, []);

이렇게 하면 화면 크기가 바뀌더라도 이미지의 사이즈를 동적으로 변화시킬 수 있다.

정석이라고 하기에는 애매하지만 원하는 기능을 구현하긴 했으니 다행이라고 생각한다...ㅎ

아래는 Modal Component 관련 코드입니다.

./components/modal.tsx

import Image from "next/image";
import styles from "../pages/modules/Modal.module.css";
import React, { useEffect, useState } from "react";

interface ModalProps {
  url: string;
  onClose: () => void;
  visible: boolean;
}

export default function Modal({ url, onClose, visible }: ModalProps) {
  const handleCloseClick = (e: any) => {
    e.preventDefault();
    onClose();
  };

  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const [margin, setMargin] = useState(0);

  const modalSizing = () => {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    if (windowWidth > windowHeight) {
      setHeight(windowHeight * 0.8);
      setWidth((windowHeight * 0.8) / 1.414);
      setMargin(windowHeight * 0.1);
    } else {
      setWidth(windowWidth * 0.8);
      setHeight(windowWidth * 0.8 * 1.414);
      setMargin((windowHeight - windowWidth * 0.8 * 1.414) / 2);
    }
  };

  useEffect(() => {
    modalSizing();
    window.addEventListener("resize", modalSizing);
    return () => {
      window.removeEventListener("resize", modalSizing);
    };
  }, []);

  return (
    <>
      <div className={styles.modal} style={{ display: `${visible ? "block" : "none"}` }}>
        <div className={styles.modal_close} onClick={handleCloseClick}>
          창 닫기 ☓
        </div>
        <div className={styles.modal_body}>
          <Image src={`/sessions/${url}.webp`} alt="modal-img" width={width} height={height} className={styles.modal_img} style={{ marginTop: margin }} />
        </div>
      </div>
    </>
  );
}

./pages/modules/Modal.module.css

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
  z-index: 100;
  margin: auto;
}
.modal_body {
  position: absolute;
  top: 50%;
  left: 50%;
  padding: 40px;
  text-align: center;
  background-color: transparent;
  border-radius: 10px;
  transform: translateX(-50%) translateY(-50%);
  animation: showModal 0.5s;
  height: 100%;
  width: 100%;
  box-shadow: none;
}
@keyframes showModal {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(-60%);
  }
  100% {
    opacity: 1;
    transform: translateX(-50%) translateY(-50%);
  }
}
.modal_close {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 100px;
  height: 40px;
  font-size: 17px;
  background-color: #fff;
  border-radius: 35px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  margin: 0;
  color: #222;
  z-index: 100;
}
.modal_img {
  border-radius: 10px;
  box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15);
  object-fit: contain;
}
profile
공부하는 개발자 꿈나무

0개의 댓글