본 홈페이지를 보면 슬라이드가 갖고 있는 기능은 아래와 같다.
item
만큼.item
3개와 절반 가려진 item
이 존재한다면 3개만큼의 너비만 이동한다.thumb
를 누르면 thumb
의 높이가 2배로 증가하면서 스크롤바를 붙잡은채 이동이 가능해진다.여기서 덧붙여 고려한 사항이 '해당 슬라이드가 사용되는 곳은 어디인가'이다.
사실 overflow: scroll
을 사용한다면 큰 수고 없이 스크롤 기능을 표현해 낼 수 있다. 그러나 최대한 본 홈페이지와 비슷하게 만들어보고 싶어 스크롤을 직접 구현하게 되었다.
무엇보다도 기존 css의 scroll
을 사용하면 thumb
의 너비를 조정이 불가능하다고 한다.
기본 설정으로 .slider-list {display: flex}
, .item {flex: 1 0 20vw}
을 설정해주었다.
화살표 버튼 혹은 스크롤을 클릭하면 이동할 너비를 계산해 슬라이드 영역은 margin-left
, 스크롤의 thumb
는 left
값을 바꿔주었다.
thumb
에 hover
시 높이가 두배로 증가한다.
thumb
를 클릭하면 onMouseDown
이벤트로 isThumbClicked: true
로 바꿔주고 이 값이 true인 동안 좌표 이동을 트래킹하여 슬라이드 영역과 thumb 위치를 이동시켜 준다.
반대로 마우스 클릭을 종료하면 onMouseUp
이벤트를 걸어 isThumbClicked: false
로 전환시키고 트래킹을 종료한다.
클릭시 새로 렌더 될 수 있게 이 모든 값은 state
로 저장해 관리하였다.
state
값이 변경할 때마다 화면이 리렌더링되다보니 thumb
를 붙잡고 이동시킬 때 상당한 딜레이가 발생하였다.
또한 React가 비동기로 작동하다보니 thumb
를 끌어 위치를 이동시킨 후 다른 영역을 클릭하면 thumb
가 끌려가기 전의 값을 가져와 영역 계산을 해버리는 오류가 발생해버렸다.
이를 해결하기 위해 state
를 ref
로 바꿔보려 했으나 결국 해결 방안을 찾지 못했다....
이미지 모달과 상품 리스트에서 모두 사용하기 위해 컴포넌트 속성을 다음과 같이 설정해주었다.
// 이미지 모달의 경우
<Slider
productList={imageList}
showItem="1"
itemNums={imageList.length}
selectedImg={selectedImg}
/>
// 상품 리스트의 경우
<Slider
productList={productList}
itemWidth={window.innerWidth/5}
itemNums={imageList.length}
selectedImg={selectedImg}
/>
showItem
이 존재한다면 이미지 모달로 간주하여 너비를 100%로 고정. 존재하지 않다면 itemWidth
값을 넣어준다.itemNums
가 꼭 필요하다.this.state.selectedImg: -1
을 선언해 내려보내준다. (-1: 상품 리스트 / 0 이상: 이미지 모달)제품 상세 페이지는 사이드 모달의 사용도가 매우 높았다.
state
로 관리하였다.display: none
을 주고, display: block
을 가진 또 다른 class
를 만들어 state
값에 따라 적용/미적용을 바꿔주었다.animation
으로 처리한다.사실 이 문제는 앞서 등장한 이미지 모달에서도 동일하게 발생하였다.
animation
은 오로지 해당 요소가 처음 등장할 때 적용된다.
그러다보니 display: none
을 통해 모달을 닫으면
요소 자체가 화면에서 삭제되어 그 어떠한 animation
도 적용시킬 수 없다.
이에 대한 해결방안으로는 display
가 아닌 위치 그 자체를 화면 밖에서 안으로, 안에서 밖으로 이동시켜주는 방법이 있다.
같은 형태의 모달이지만 여러 곳에서 사용되기 때문에 공통 컴포넌트로 만들어 놓고 클릭한 버튼에 따라 내용이 바뀌도록 해주었다.
switch (selectedBtn) {
case 1:
return <Description />;
case 2:
return <Size />;
case 3:
return <Review />;
case 4:
return (
<Option />
);
default:
break;
}
평점은 총 평점과 개인 평점 두 가지가 존재한다.
{등록 날짜, 리뷰 내용, 다섯가지 만족도 값}
을 필요로 한다.position: absolute
로 회색 별 위에 검정색 별이 정확하게 올라올 수 있도록 해줬다.Math.round(average * 2) / 2
를 통해 0.25 기준으로 반올림한다.flex
를 이용해 영역을 5등분 한다.div
는 4개뿐이다. 구분선을 만들기 위함이기 때문에 자식은 4개만 만들고 부모에게 justify-content: flex-start
를 준다면 5등분이 가능하다.justify-content: flex-end
와 함께 flex-basis: 20%
를 가진다.margin-right
에 점의 절반 너비만큼 음수값을 지정해주면 구분선 중심에 정확하게 올라간다.z-index
값을 밑배경 < 값 표시기 < 흰 점 순서로 해준다.총 평점의 경우 평균으로 계산된 하나의 객체 값이, 개인 평점의 경우 여러 사람들의 평점(객체 타입) 나열된 배열 값이 input
으로 들어오기 때문에 이를 해결하기 위해 모두 배열 형태로 통일하였다.
// 총 평점의 경우
<ReviewBox
id={0}
reviewList={averageObj}
reviewNum={reviewList.length}
/>
// 개인 평점의 경우
<ReviewBox
id={review.id}
reviewList={reviewList}
/>
input
값을 배열 형태로 만들었기 때문에 총 평점에 들어가는 객체는 배열 중 index
0번째로 고정된다.제품 상세 페이지에서 사용되는 경우와 리뷰 모달 내에서 사용되는 경우가 서로 다르다.
// 제품 상세 페이지에서 사용되는 경우
<Stars
reviewList={product.reviews}
reviewNum={product.reviews.length}
/>
// 리뷰 모달 내에서 사용되는 경우
<Stars
id={id}
reviewList={thisReviewList}
reviewNum={reviewNum}
/>
reviewNum
의 여부로 판단한다.<ReviewBox />
에서 <Stars />
로 state
가 전달되기 때문에 개인 평점에서 처럼 reviewNum
을 애초부터 전달받지 못했다면 <Stars />
의 this.props.reviewNum
값은 당연히 false
이다.id
값의 여부로 구분된다.id
값이 존재한다면 배열의 해당 index
값을 지정하여 사용. 그렇지 않다면 <Stars />
컴포넌트 내에서 자체적으로 평균값을 계산한다.개인적으로 이 프로젝트를 진행하면서 가장 신경 쓴 부분이었다. 코딩에 들어가기 앞서 페이지 분석을 통해 공통적으로 사용될 수 있는 부분이 어디인가를 파악하고, 동일한 형태라면 최대한 재사용할 수 있도록 노력하였다.
프론트엔드 엔지니어만 무려 4명이 참여하는 프로젝트이기 때문에 Sass Nesting
을 통해 Git Merge
를 했을 때 서로의 CSS
가 문제를 일으켜 화면이 깨지는 것을 방지했다.
나름 잘 지었다고 생각한 변수명을 타인이 읽고나서 '이게 뭘 가리키는 건가요?'라는 질문을 받았을 때 순간적으로 말문이 막혀버렸다.
생각해보면 id
라는 변수만 해도 어떤 것의 id
인지를 하나도 명시하지 않았었다..!
앞으로는 이름 짓는 데 훨씬 더 신중할 필요가 있어보인다.
백엔드가 정해서 주면 프론트엔드가 그 내용에 맞추겠다는 생각하에 소통을 등한시 했다는 점이 이 프로젝트에서 가장 후회하는 부분이다.
시간이 걸리더라도 내가 이 페이지에서 어떤 데이터들이 필요하고, 백에서 어떤 이름으로 보낼 것인지를 미리 정확하게 정하고 갔다면 오히려 후에 수정할 일 없이 스무스한 진행이 가능했을 거란 생각이 프로젝트 내내 남아있었다.
2주간 정말 쉴틈없이, 그리고 끊임없이 코드에 대한 고민을 했다. 어떻게 하면 내가 직면한 문제를 해결할 수 있을지를 고민하고, 어떻게 하면 시간 안에 목표한 지점까지 도달할 수 있을지를 고민했다.
프로젝트는 늘 기한이 주어진다. 나 혼자 취미로 하는 것이 아닌 이상 기한은 정해져있고 그것을 준수해야만 한다. 어찌 보면 욕심을 버리는 일이 가장 중요한게 아닌가 싶다.
Agile 방식은 '버전1'을 만들고, 이를 진화시켜 '버전2'를 만들고, 또 이를 진화시켜 '버전3'을 만들어낸다. 기본 구성을 먼저 갖춘 후에 기능을 추가해도 될 일을 당장 눈앞에 보이는 것을 해내고 싶다는 욕심때문에 오히려 계획이 어그러진다는 것은 마음을 상당히 힘들게 만들었다.
이 다음의 프로젝트에서는 부디 내가 스스로 그 욕심을 잘 다스릴 수 있기를 응원한다.
p.s. 우리 팀 모두 정말 수고 많았고 2주동안 함께 달려주고 서로 지탱해준 점에 매우 감사합니다.