포트폴리오 프로젝트 작업하다가 modal로 프로젝트 리드미를 만들려고 했다.
모달까지는 만들었는데 뒤에 배경이 스크롤이 계속 휙휙 넘어가서 이것을 막아야겠다 생각해서 검색을 통해 만들게 되었다.
// 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>
)
}
Next.js 13버전을 사용했고, 13버전부터 구조가 많이 바뀌었다. 13버전은 layout.tsx 부분이 html부분이기 때문에 body아래에 div.id를 만들어 주었고
따로 Modal파일을 만들어서 children을 받아와서 렌더링 되게 만들었다.
=> react와 사실 거의 비슷하기때문에 조금의 검색으로도 충분히 만들 수 있다.
// 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
scroll-behavior: smooth;
을 주었기 때문에 위에 utill/scroll
코드가 모달을 닫으면 원래 위치로 돌아오는 코드가 포함이 되어있다.사실 css로 쉽게 스크롤 hidden주고 다시 unset주면 되지만 저렇게 하는 이유는 사파리와 ios에서는 호환이 안되고, 옆에 스크롤이 생겼다 사라지다보니 전체적으로 조금씩 크기가 바뀌어 보기불편?이 있었다.
next.js 13버전 modal-portal
next.js 13버전 modal-portal & 스크롤 막기