[프로젝트] next.js modal-portal / modal시 배경 scroll막기(Next.js, portal, useHooks)

lee·2023년 12월 16일
1
post-thumbnail

1. 사용 상황

포트폴리오 프로젝트 작업하다가 modal로 프로젝트 리드미를 만들려고 했다.
모달까지는 만들었는데 뒤에 배경이 스크롤이 계속 휙휙 넘어가서 이것을 막아야겠다 생각해서 검색을 통해 만들게 되었다.

2. modal-portal 만들기

// components/modal/ProjectModal.tsx

import usePreventScroll from "@/hooks/usePreventsScroll"
import { ReactNode } from "react"
import ReactDOM from "react-dom"

interface ModalProps {
  open: boolean
  onClose: () => void
  children: ReactNode
}

function ProjectModal({ open, onClose, children }: ModalProps) {
  usePreventScroll(open)
  if (!open) return null
  return ReactDOM.createPortal(
    <>
      <div className="fixed top-0 left-0 right-0 bottom-0 bg-black bg-opacity-30 z-50" />
      <div className="fixed top-[50%] left-[50%] p-[50px] bg-white z-50 translate-x-[-50%] translate-y-[-50%]">
        <button onClick={onClose}>모달 닫기</button>
        {children}
      </div>
    </>,
    document.getElementById("project-modal") as HTMLElement,
  )
}

export default ProjectModal
// app/layout.tsx

import "./globals.css"
import type { Metadata } from "next"
import Footer from "./pages/Footer"
import { notoSansKr, roboto } from "./font"
import Nav from "./pages/Nav"

export const metadata: Metadata = {
  title: "Seyoung's Portfolio",
  description: "신입 개발자 이세영의 포트폴리오 페이지 입니다.",
  icons: {
    icon: "/images/favicon.ico",
  },
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="kr" className={`${notoSansKr.variable} ${roboto.variable}`}>
      <body className="w-full h-full box-border">
        <header className="flex items-center justify-center">
          <Nav />
        </header>
        <main>{children}</main>
        <div id="project-modal"></div>
        <footer>
          <Footer />
        </footer>
      </body>
    </html>
  )
}

설명

  1. Next.js 13버전을 사용했고, 13버전부터 구조가 많이 바뀌었다. 13버전은 layout.tsx 부분이 html부분이기 때문에 body아래에 div.id를 만들어 주었고

  2. 따로 Modal파일을 만들어서 children을 받아와서 렌더링 되게 만들었다.

=> react와 사실 거의 비슷하기때문에 조금의 검색으로도 충분히 만들 수 있다.

3. modal-scroll 막기

// utill/scroll.ts

export const preventScroll = () => {
  const currentScrollY = window.scrollY
  document.body.style.position = "fixed"
  document.body.style.width = "100%"
  document.body.style.top = `-${currentScrollY}px` // 현재 스크롤 위치
  document.body.style.overflowY = "scroll"
  document.documentElement.style.scrollBehavior = "auto"
  return currentScrollY
}

export const allowScroll = (prevScrollY: number) => {
  document.body.style.position = ""
  document.body.style.width = ""
  document.body.style.top = ""
  document.body.style.overflowY = ""
  window.scrollTo(0, prevScrollY)
}
// hooks/usePreventsScroll.ts

import { useEffect } from "react"
import { preventScroll, allowScroll } from "@/util/scroll"

const usePreventScroll = (open: boolean) => {
  useEffect(() => {
    if (open) {
      const prevScrollY = preventScroll()
      return () => {
        allowScroll(prevScrollY)
      }
    } else {
      document.documentElement.style.scrollBehavior = "smooth"
    }
  }, [open])
}

export default usePreventScroll

설명

  1. useHook으로 만들어준 부분은 위에 modal 컴포넌트에 사용된다. open을 매개변수로 받아서 open이 됐을때만 실행이 되게 설정을 해주었다.
  2. 가장 중요한 부분 이거때문에 이 블로그를 적는것이다!!!!
    상황 설명 : 포트폴리오 프로젝트상 헤더부분을 클릭하면 스크롤이 스무스하게 가야하기 때문에 globals.css에 html부분에 속성으로 scroll-behavior: smooth;을 주었기 때문에 위에 utill/scroll코드가 모달을 닫으면 원래 위치로 돌아오는 코드가 포함이 되어있다.
    => 근데 여기서 맨위에를 찍고 내려오는데 스크롤이 smooth다 보니 천천히 내려오게 되는 것이다.
    그래서 dom.style조작을 통해 atuo로 정해주고 hook에서 다시 smooth로 바꾸어 줬다.

사실 css로 쉽게 스크롤 hidden주고 다시 unset주면 되지만 저렇게 하는 이유는 사파리와 ios에서는 호환이 안되고, 옆에 스크롤이 생겼다 사라지다보니 전체적으로 조금씩 크기가 바뀌어 보기불편?이 있었다.

도움 & 참고 블로그

next.js 13버전 modal-portal
next.js 13버전 modal-portal & 스크롤 막기

profile
초보 코딩

0개의 댓글