일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- typscript
- tailwindcss
- textarea autosize
- nextjs
- 알고리즘
- react
- never타입
- 그리디
- 라이프사이클
- 큐
- aws lightsail
- TypeScript
- Algorithm
- nestjs
- next.js
- 스택
- NextAuth
- 버블정렬
- 해쉬
- js알고리즘
- 슬라이딩윈도우
- 빅오
- styled-components
- 블로그만들기
- 투포인터
- react-query
- JavaScript
- cookie
- 정렬
- isNaN
- Today
- Total
far
[React + Typescript] Modal창 만들기 (+ 스크롤 고정) 본문
과거에 만들었던 개인 프로젝트를 보다 보니까 내가 사용하면서도 UX가 안좋다는 생각이 들 정도로 불필요한 링크 이동이 있었다. 그래서 조금이라도 UX적으로 좋게 만들기 위해 간단한 정보들은 링크 이동 대신 모달창을 사용해 정보를 보여주기로 했다.
여태까지 모달창은 Bootstrap이나 TailwindCSS에서 등에서 제공하는 걸 사용해 왔는데, 바꾸는 김에 내가 만들어보는 것도 나쁘지 않겠다고 생각해 작업을 해보았다.
index.tsx
// 설명하는데 불필요한 코드는 전부 생략했다.
import Project from '../components/Project';
import Aco from '../components/Aco';
export default function Home() {
const [modal, setModal] = useState(false);
const onModalToggle = useCallback(() => {
setlModal((prev) => !prev)
}, [])
return (
<main>
<div className={`${modal? 'modal-backdrop show' : 'none'}`}>
<Aco onModal={onModalToggle} />
</div>
<section id="project">
<Project
onModal={onModalToggle}
/>
</section>
</main>
)
}
modalToggle을 만들어 클릭 버튼이 있는 컴포넌트로 props를 내려준다. 그리고 클릭 버튼을 눌렀을 때 작동하고 싶은 모달창을 CSS로 제어해준다.
project.tsx
interface props {
onModal: () => void;
}
const Project = ({ onModal }: props) => {
return (
<>
<div className="project__title">
<div className="project__title-name">Project</div>
</div>
<div className="menu">
<div className="menu__item">
<button
className="project__menu__item-link"
onClick={onModal}
>Aco</button>
</div>
</div>
</>
)
}
Open 클릭 버튼이 있는 컴포넌트
aco.tsx
interface props {
onModal: () => void;
}
const Aco = ({ onModal }: props) => {
return (
<div className="menu">
<div className="menu__item">
<button>
<i className="bi bi-x" onClick={onModal}></i>
</button>
</div>
</div>
)
}
Close버튼이 있는 컴포넌트.
modal.scss
.modal-backdrop.show {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 0;
z-index: 1050;
width: 100vw;
height: 100vh;
background-color: #ededed89;
}
.modal {
position: relative;
border: solid 1px gray;
z-index: 1055;
width: 95%;
height: 95%;
overflow-x: hidden;
overflow-y: auto;
outline: 0;
background-color: white;
animation: TranslateIn 0.2s ease-in-out;
}
@keyframes TranslateIn {
0% {
transform: translateY(-500px);
opacity: .5;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
.none {
display: none;
}
부모 엘리트먼트의 .modal-backdrop클래스에서 fixed와 flex로 모달창이 화면 정 중앙에 위치할 수 있도록 자리를 잡아주었고, 하위 엘리먼트인 .modal클래스에서 모달의 크기를 잡아주었다. 또, .modal에 걸 애니메이션과 none클래스가 선택 됐을 때 display를 숨겨주는 CSS를 작성해주었다.
여기까지 작성을 해주면 기본적인 모달창은 완성이 된다. 하지만 모달창에 띄울 컨텐츠가 길어져서 스크롤바가 생길 경우, 모달창 내부의 스크롤바가 끝에 위치해 있을 때 마우스 휠을 당기게 되면 전체 창의 스크롤바가 이동되는 현상이 발생했다. 그래서 모달창이 실행될 때 전체 페이지의 스크롤바를 작동 되지 않게 만들기로 했다.
index.tsx
// 설명하는데 불필요한 코드는 전부 생략했다.
import Project from '../components/Project';
import Aco from '../components/Aco';
export default function Home() {
const [modal, setModal] = useState(false);
let currentScroll = 0;
const lockScroll = useCallback(() => {
currentScroll = window.scrollY;
document.body.style.overflowY = 'hidden';
document.body.style.top = `${currentScroll}px`;
}, [currentScroll]);
const openScroll = useCallback(() => {
document.body.style.removeProperty('overflow');
document.body.style.removeProperty('top');
window.scrollTo(0, currentScroll)
}, [currentScroll]);
const onModalToggle = useCallback(() => {
setlModal((prev) => !prev)
if (!modal) {
lockScroll()
} else {
openScroll()
}
}, [modal, lockScroll, openScroll])
return (
<main>
<div className={`${modal? 'modal-backdrop show' : 'none'}`}>
<Aco onModal={onModalToggle} />
</div>
<section id="project">
<Project
onModal={onModalToggle}
/>
</section>
</main>
)
}
document.body를 직접 찍고 싶지는 않았지만 대체할 수 있는 방법을 찾지 못하여 이대로 작성하게 되었다.
일단 scrollY의 위치를 기억해두고 모달 open시 body의 scroll을 없애버린 뒤 close를 눌렀을 때 프로퍼티를 삭제해서 기억해둔 스크롤의 위치로 이동하는 코드를 추가해주었다. style.top은 이게 없으면 애니메이션 작동시 전체 페이지의 스크롤이 0, 0위치로 이동한 것이 보이기 때문에 추가해주었다.
하지만 이렇게 작성할 경우 스크롤바가 사라졌을 때 전체 페이지가 스크롤바가 사라진 만큼 움직이게 되어 조금 거슬리는 모양새가 된다.
// 설명하는데 불필요한 코드는 전부 생략했다.
import Project from '../components/Project';
import Aco from '../components/Aco';
export default function Home() {
const [modal, setModal] = useState(false);
let currentScroll = 0;
const lockScroll = useCallback(() => {
currentScroll = window.scrollY;
document.body.style.overflowY = 'scroll';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
document.body.style.top = `${currentScroll}px`;
}, [currentScroll]);
const openScroll = useCallback(() => {
document.body.style.removeProperty('overflow');
document.body.style.removeProperty('position');
document.body.style.removeProperty('width');
document.body.style.removeProperty('top');
window.scrollTo(0, currentScroll)
}, [currentScroll]);
const onModalToggle = useCallback(() => {
setlModal((prev) => !prev)
if (!modal) {
lockScroll()
} else {
openScroll()
}
}, [modal, lockScroll, openScroll])
return (
<main>
<div className={`${modal? 'modal-backdrop show' : 'none'}`}>
<Aco onModal={onModalToggle} />
</div>
<section id="project">
<Project
onModal={onModalToggle}
/>
</section>
</main>
)
}
그래서 어쩔 수 없이 overflow: scroll과 fixed를 사용해 스크롤바를 잠궈버리고 width가 변하지 않게 만들었다.
이제 모달창을 껐다 켜도 거슬리는 부분 없이 잘 동작한다.
좀 더 좋은 코드가 있을 것 같다는 생각이 드는 작업이었지만.. 어쨋든 완성이다.
'React > 기록' 카테고리의 다른 글
[React] input에서 한 글자 입력 후 포커싱이 풀리는 현상 (0) | 2024.07.10 |
---|---|
[React + websocket] Sock.js와 Stomp.js로 간단한 채팅 구현하기 (0) | 2023.04.28 |
[React] Checkbox와 API연결하기 (첫 클릭시 Boolean 전환 안되는 현상 해결) (0) | 2023.04.15 |
[React + Typescript] Intersection Observer로 간단한 스크롤 애니메이션 만들기 (텍스트 가로 이동, Fade In/Out) (0) | 2023.03.30 |
[React] Key에 대하여 (Map의 key props) (0) | 2023.03.15 |