Modal 만들기
프로젝트에서 모달창을 구현해야하는 일이 생겼다. 라이브러리를 이용해도 좋지만 직접 구현하는 것이 더 학습에 도움이 될 것 같아서 모달만들기 글 를 참고하여 직접 구현을 해보았고 만족스러운 결과가 나왔다.
1. 구조
모달창의 구조는 DImmer, OuterContainer, InnerContainer, Icon으로 구성된다. 각 요소의 역할은 다음과 같다.
- Dimmer
- 화면을 0.3투명도의 검은 화면 Div로 채운다.
- 다른 컴포넌트들보다 위에 위치 한다.(z-index)
- OuterContainer
- InnerContainer를 감싸고 있다.
- position을 fixed로 주어 스크롤을 내려도 위치를 유지시킨다.
- Dimmer보다도 더 위에 위치한다.
- InnerContainer
- Dimmer위에 떠있는 하얀 Div를 의미하며 InnerContainer의 Children에 따라 다른 컨텐츠의 Modal이 된다.
- positon을 relative, top = 50%로 주어 가운데 위치를 유지시킨다.
- transform을 translateY(-50%)로 주어 스크롤이 있는 페이지에서도 가운데 위치를 유지시킨다.
- Icon
- InnerContainer의 Children 중 하나로 클릭하면 모달창이 닫히는 구조를 갖게한다.
2. Styled Component
작업 도중 경고창이라는 새로운 모달창이 필요해졌다. 위의 모달과 이 경고창은 같은 모달이지만 다른 종류의 용도를 갖고 있으므로 색상, 애니메이션 등이 달라져야했다. 이 부분은 Styled Component를 이용하여 해결할 수 있었다.
import { Dimmer, OuterContainer, InnerContainer, Icon } from './CenterModalStyle';
const CenterModal = ({ visible, color, isBlackBtn, onClose, className, children, backColor, isWarning }) => {
return (
<>
<Dimmer visible={visible} backColor={backColor}></Dimmer>
<OuterContainer tabIndex="-1" visible={visible}>
<InnerContainer tabIndex="0" color={color} className={className} isWarning={isWarning}>
{children}
<Icon bg={isBlackBtn ? './res/close_black.png' : './res/close_white.png'} onClick={onClose}></Icon>
</InnerContainer>
</OuterContainer>
</>
);
};
Styled Component는 css에 js 변수를 전달할 수 있는 라이브러리이다. 따라서 나는 모달 창을 생성할 때 color, isWarning 등의 props를 받아 여러 종류의 모달창을 구분하여 생성할 수 있게 만들었다.
import styled, { css, keyframes } from 'styled-components';
const shakeAnimation = keyframes`
0%, 100% { transform: translate(0, -50%); }
10%, 30%, 50%, 70%, 90% { transform: translate(-10px, -50%); }
20%, 40%, 60%, 80% { transform: translate(10px, -50%); }
`;
export const Dimmer = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
//...생략
background-color: ${(props) => (props.backColor ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 0.3)')};
`;
export const OuterContainer = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
//...생략
`;
export const InnerContainer = styled.div`
background-color: ${props => props.color};
//...생략
${(props) => props.isWarning && css`
animation: ${shakeAnimation} 0.3s alternate;
`}
`;
export const Icon = styled.div`
//...생략
background-image: url(${props => props.bg});
`;
Styled Component를 이용하면 다른 css가 적용된 HTML 요소들을 마치 컴포넌트처럼 사용할 수 있다. 컴포넌트처럼 props를 전달하면 css에서 이를 props로 받아 해당 요소의 값에 따라 다른 css를 적용할 수 있다.
3. Close
import { Dimmer, OuterContainer, InnerContainer, Icon } from './CenterModalStyle';
const CenterModal = ({ visible, color, isBlackBtn, onClose, className, children, backColor, isWarning }) => {
const onClickDimmerHandler = () => {
onClose();
};
const prevendEventPropagation = (e) => {
e.stopPropagation();
};
return (
<>
<Dimmer visible={visible} backColor={backColor}></Dimmer>
<OuterContainer tabIndex="-1" visible={visible} onClick={onClickDimmerHandler}>
<InnerContainer tabIndex="0" color={color} className={className} isWarning={isWarning} onClick={prevendEventPropagation}>
{children}
<Icon bg={isBlackBtn ? './res/close_black.png' : './res/close_white.png'} onClick={onClose}></Icon>
</InnerContainer>
</OuterContainer>
</>
);
};
모달창은 보통 모달창 밖을 클릭하면 닫히게 되어있다. 나도 이러한 액션을 모달에 적용시켜보았다.
모달창을 닫을 때 중요한 점은 모달 안쪽을 클릭하면 닫히지 않고, 바깥쪽을 클릭했을 때만 모달이 닫힌다는 점이다. 나는 일단 OuterContainer를 클릭하면 모달이 닫히게 만들 되 InnerContainer를 클릭하면 prevendEventPropagation함수를 실행시켜 이벤트 전달을 막아 모달창이 닫히지 않도록 구현하였다.