[React] Masonry 컴포넌트 만들기

Bling
4 min readJul 24, 2022

--

unsplash main page gallery screenshot
Unsplash의 홈 화면 갤러리 영역 스크린샷

22.10.15 추가됨
주의사항: 아래 설명된 방식으로 Masonry를 구현하는 방식은 접근성과 사용성이 크게 떨어지는 방식으로 절대 추천하지 않습니다. 이 내용에 관한 자세한 반성문은 이 블로그 포스팅을 참고해주세요.

다양한 형식으로 페이지를 레이아웃 하다보면 때로는 CSS에서 기본적으로 제공되는 형식만으로는 만족하기 어려울 때가 있다. 다행히 Flex와 Grid 같은 비교적 새로운 문법들을 통해서 1차원 그리고 2차원으로 나열하는 것은 수월한 편이다.

하지만 2차원으로 나열하고자 하는 컨텐츠에 크기가 가지각색이고 그 컨텐츠를 크기를 변경하지 않고자 하는 경우 일종의 딜레마에 빠지게 된다. Grid의 경우 기본적으로 한 행이 그 행의 가장 큰 높이를 가진 자식 요소의 높이를 선택하게 되기 때문이다. 따라서 Grid 만으로 위 사진에서처럼 다음 컨텐츠가 같은 열의 이전 행에 있는 요소 바로 아래에 붙도록 구현하는 것은 불가능에 가깝다. 위 사진과 같은 배치를 Masonry 레이아웃이라고 칭한다.

Brick in masonry layout
Photo by Hayden Mills on Unsplash — 원래 Masonry는 이런 벽돌을 쌓는 형태의 조적조를 의미한다

이번에 프로젝트를 구현하면서 정확히 이 경우를 마주했다. 키보드의 사진과 이름을 나열하는 컴포넌트를 구현하고자 했는데 그 크기는 일정하게 맞추지 않기로 했다. 상품 사진의 원본 크기를 최대한 그대로 유지하고자 했으며 세세한 옵션에 따라서 다른 제품으로 분류되는 키보드의 특성 상 제품명도 일관되게 생략하는 것이 불가능했다.

위 상품에서 제품명의 끝 부분을 생략한다면 키보드의 큰 특징 중 하나인 축에 대한 정보가 생략된다.

따라서 모든 상품 컴포넌트가 제각각의 높이를 가지고 이를 자연스럽게 배치하기 위해서 Masonry 레이아웃으로 배치하기로 결정했다.

구현하기

실제 구현은 단순한 생각에서 시작했다. Grid로 위와 같은 형태를 만들 수 없다면 1차원 방향의 Flex를 두 번 사용해서 만드는 방법이다. 즉 열을 Column 방향의 Flex로 만든 뒤, 이 열을 묶어 Row 방향으로 Flex로 행을 만드는 방법이다.

빨간색을 하나의 Flex로 다시 파란색으로 하나의 Flex로 묶는다

CSS의 레이아웃으로는 이것으로 충분했다. 하지만 문제는 내용물을 읽는 순서 (1–2–3–4–5–6)과 빨간색 Flex로 묶이는 영역의 순서(1–4–2–5–3–6)가 일치하지 않기 때문에 하나의 컨테이너로 묶기 위해서 순서를 계산하는 추가적인 작업이 필요했다. 즉 2차원 배열에 원하는 열의 개수로 나눴을 때의 나머지에 따라서 배분해서 넣어주고 각 배열을 위처럼 묶어서 처리하면 되는 상황이었다.

elements와 columnCount가 이미 존재한다는 전제 하에 작성된 예시

이 방식을 반복적으로 사용하면서 구현해도 큰 문제는 없지만 깔끔하게 리팩토링 한 뒤 이를 재사용 가능한 컴포넌트로 만들면 이 레이아웃이 필요할 때마다 간단하게 이 컴포넌트만 불러오면 된다.

위 예시에서는 Styled Components로 스타일링을 적용했다.

여기서 또 한 가지 눈 여겨 볼 점은 React.Children이라는 최상위 API을 통해서 Prop으로 전달받는 children을 배열처럼 다룬다는 점이다. 그 중에서도 자식 컴포넌트를 배열로 만들어주는 React.Children.toArray() 매서드를 활용했다.

위와 같은 컴포넌트를 만들어두면 이 레이아웃을 사용하고자 할 때 자식 컴포넌트로 나열할 요소들을 전달하고 Prop으로 열의 개수만 전달하면 자동으로 Masonry 레이아웃을 구현할 수 있다.

--

--