# 계획
About part에 포함하고자 하는 내용은 다음과 같다.
- 내 사진
- 컨셉과 이어지는 내 소개
- 커리어: 회사와 했던 일 간단히 설명
- 스킬 셋: 무슨 기술을 쓸 줄 아는지
- 학력
그런데, 2번이 나머지 항목과 좀 동떨어진다.
그렇다고 다양한 저걸 위한 파트를 하나 더 만들자니 지면이 아까운 느낌이다.
그래서 2번은 팝업 형식으로 따로 빼서 구현하기로 하였다.
간단한 도식은 다음과 같다.
# 구현
- 레이아웃
화면은 크게 상단과 하단 두 부분으로 구성된다.
상단 소개문구와 사진은 flex, 하단 설명 파트는 grid를 이용해 구현하였고 내부적으로 flex, grid를 중첩하여 레이아웃을 구성하였다.
레이아웃 구성하는 부분은 딱히 특이사항이 없어
이런 형태로 구현하였다.
- 팝업
소개글 아래 버튼을 누르면 팝업 형식으로 부가 정보를 보여주기를 원했다.
다만, 실제로 새로운 팝업 창을 띄우는 식이 아닌 화면 상 Component를 보여주는 형식으로 구현하였다.
팝업 컴포넌트를 만들고, 초기 opacity 값을 0으로 설정해 평소에는 보이지 않다가, 버튼을 누르면 opacity 값을 1로 바꿔 팝업창이 나타나는 효과를 구현하였다.
팝업창을 닫는 것은 반대로 opacity를 0으로 바꿔주는 것으로 구현하였으며, 팝업 영역 밖을 클릭하거나 팝업 내의 close button을 클릭하면 닫히도록 하였다.
여기서 React의 특성에 의한 특이사항이 하나 있었는데,
화면 밖 영역을 클릭하면 팝업이 닫히게 하기 위해 closePopup method를 구현해 document.body의 onClick eventlistener로 등록하였으나, 그 이후 팝업이 나타나지 않는다.
그 원인은 버튼이 눌림과 동시에 이벤트 버블링으로 인해 클릭 이벤트가 document.body까지 전달되고 closePopup이 실행되기 때문이다.
이를 해결하고자 버튼 클릭 시에는 stopPropagation을 호출하였으나, 해결되지 않는다.
그 원인은 React component에 등록한 event listener는 React의 synthetic event로 취급되며 root인 document로 올려 관리하기 때문이다.
반면, body에 직접 추가한 closePopup은 native event로 root가 아닌 body가 직접 관리하게 된다.
따라서 우리가 기대한 것은 button onClick시 stopPropagation이 호출되어 body에 이벤트가 전달되지 않는 것이지만,
body의 closePopup이 stopPropagation보다 앞선 시점에서 실행되기 때문에 아무런 동작을 하지 않게 된다.
해결 방법은 크게 2가지로,
1. popupClose를 직접 DOM에 등록하는 것이 아닌, 상위 단의 ReactDOM에 등록한다.
예를 들면, 내 프로젝트의 최상위에 위치한 Home의 div에 onClick eventlistener를 추가한다.
2. popupClose를 root에 등록하고 nativeEvent.stopImmediatePropagation()를 사용한다.
stopImmediatePropagation은 같은 element 같은 event type에 붙은 event listener가 여러개일 때 다른 event listener를 동작하지 않도록 막는다.
아래와 같이 event listener 내부에서 사용하면 된다.
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
참고: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation
결론적으로 나는 2번을 선택하였다.
React 구조를 생각하면 native event를 되도록이면 사용하지 않는 것이 최선이지만, 나는 popup 관련된 코드를 하나의 Component에서 관리하고 싶었고, 무엇보다 Home이 군더더기 없이 깔끔했으면 했다.
- 팝업 내부 이미지 슬라이드
팝업의 왼쪽 사진은 슬라이드 쇼 형태로 여러 이미지를 보여주고자 하였다.
JS로 구현하는 방법과 CSS animation을 이용하는 방법이 있는데, 나는 팝업을 열 때 항상 첫 번째 이미지부터 보여주고 싶었고, 팝업이 닫혀있을 때에는 불필요하게 슬라이드가 실행되지 않기를 원했기에 JS를 사용하는 방법을 택하였다.
const gallery = document.getElementById('gallery');
if (!gallery) return;
//initial setting
(gallery.children.item(0) as HTMLElement).style.zIndex = '1';
(gallery.children.item(1) as HTMLElement).style.transform =
'translateX(100%)';
for (let i = 2; i < gallery.children.length; i += 1) {
(gallery.children.item(i) as HTMLElement).style.zIndex = '-1';
}
const slide = () => {
before = gallery.children.item(state) as HTMLElement;
cur = gallery.children.item((state + 1) % itemNum) as HTMLElement;
next = gallery.children.item((state + 2) % itemNum) as HTMLElement;
if (!before || !cur || !next) return;
before.style.transform = 'translateX(-100%)';
before.style.zIndex = '0';
cur.style.transform = 'translateX(0%)';
cur.style.zIndex = '1';
next.style.transform = 'translateX(100%)';
next.style.zIndex = '-1';
state = (state + 1) % itemNum;
};
setInterval(slide, 4000)
HTML상에서 슬라이드 될 모든 이미지는 gallery class div의 child이다.
slide 함수를 일정한 interval로 실행되도록 하였다.
slide 호출 시 사라져야 할 이미지(before), 나타나야 할 이미지(cur), 다음 slide 호출 시 나타날 이미지(next)를 x 좌표 상에서 나란히 위치하도록 translateX 함수를 호출하였으며, 이미지가 이동하는 중 before, cur, next가 애매하게 화면에 표시되는 상황을 방지하기 위해 zIndex를 조절해주었다.
'Projects > Portfolio' 카테고리의 다른 글
Next.js 포트폴리오 페이지 제작기 - 6. Contact & Navigation (0) | 2023.08.09 |
---|---|
Next.js 포트폴리오 페이지 제작기 - 5. Projects part (0) | 2023.08.05 |
Next.js 포트폴리오 페이지 제작기 - 3. Intro part (0) | 2023.07.28 |
Next.js 포트폴리오 페이지 제작기 - 2. Welcome to Intro (0) | 2023.07.28 |
Next.js 포트폴리오 페이지 제작기 - 1. Welcome page 가구현 (0) | 2023.07.25 |