DYO 공부하는 블로그
Pinterest같은 Masonry 레이아웃 만들기 본문

Masonry란?

Masonry는 사전적으로는 벽돌 쌓기입니다. 아이템들을 높이가 다른 벽돌을 쌓는 것처럼 쌓아주는 레이아웃을 의미합니다.
flex, grid레이아웃은 줄 단위로 정렬하기 때문에 높낮이가 다른 아이템을 정렬할 때, 아래 공간이 남는 문제가 발생하게 됩니다. 이러한 문제를 해결하기 위해 Masonry 레이아웃이 등장했습니다.

비율을 계산해 이미지를 잘리지 않도록 고정 width에 고정하는 것은 어렵지 않지만, 결국 문제는 "배치를 어떻게 할 것인가." 입니다.
// ...
const aspectRatio = imageWidth / imageHeight;
const height = width / aspectRatio;
return (
<div
onClick={onClick}
style={{ width: width, height: height }}
className={tw(
`bg-gray-100 rounded-md relative cursor-pointer group`,
className
)}
>
)
// ...
구현 아이디어와 함께 배치 방법을 살펴보겠습니다.
구현 아이디어
- flex 레이아웃에 flex-cols를 적용해 각 줄별 배치를 자연스럽게 구현하는 방법

이 경우는 각각의 줄이 독립된 상태로, 배치 방향에 따라 자연스럽게 직전 요소에 다음 아이템이 달라붙는다.가장 단순하게 구현할 수 있고, 순서를 보장하는 것도 어렵지 않으나, row 한 쪽에 아이템 높이가 높은 것들만 배치된다면 어색한 구조로 배치될 수 있다는 문제가 있습니다.

위와 같이 아이템의 높이를 고려하지 않았을 경우, 한 열만 튀어나오는 문제가 발생할 수 있습니다. 이와 같은 문제를 해결하기 위해 각각의 열의 높이를 계산하고 배치함으로써, 평균적인 높이를 보장할 수 있습니다. 이 경우에는 순서를 보장하지 않습니다.
- 어떻게 구현할까
function arrangeMasonry(items, columnCount, gap = 16) {
const columnHeights = new Array(columnCount).fill(0);
const columns = new Array(columnCount).fill(null).map(() => []);
items.forEach((item) => {
// 가장 낮은 컬럼 찾기
const shortestColumnIndex = columnHeights.indexOf(
Math.min(...columnHeights)
);
// 해당 컬럼에 아이템 추가
columns[shortestColumnIndex].push(item);
// 컬럼 높이 업데이트
columnHeights[shortestColumnIndex] += item.height + gap;
});
return columns;
}
아이템 배치를 연산할 때, 아이템 개수 기준이 아닌 높이 기준으로 연산합니다. 가장 높이가 낮은 열에 아이템을 배치함으로써 위와 같은 문제를 해결할 수 있습니다.
- grid의 masonry속성 적용해 구현하는 방법
놀랍게도 CSS grid 속성에 masonry 속성이 존재합니다.
.grid {
display: grid;
gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
grid-template-rows: masonry;
}
그런데 실험적 기능이고, 브라우저 업데이트에 맞춰 적용되지 않고 있는 상태처럼 보입니다.

MDN 문서에서도 예시 코드가 정상적으로 작동하지 않는 모습을 확인할 수 있습니다.

역시 예상대로, 호환성이 나쁜 실험적 기능이라는 것을 확인할 수 있습니다.
- absolute를 이용해 각각의 아이템의 배치를 지정하는 방법
각각의 아이템을 absolute로 지정해 realative 컨테이너에 배치하는 방법도 있습니다. 이 경우는 각각의 아이템의 top, left 속성을 지정해 배치를 결정합니다.
배치 흐름은 아래 그림과 같습니다.

위와 같이 전체 컨테이너에서 row 컨테이너를 관리하지 않고, 각각의 아이템에 대해 배치해주는 방법입니다.
이 방법의 장점은
열로 나누어진 컨테이너를 관리하지 않아도 된다.
transform X, Y로 리플로우를 유발하지 않고 배치할 수 있다
특히 장점이라고 할 수 있는 것은 transform을 이용한 방법으로 구현할 수 있기 때문에 반응형 작업으로 아이템의 위치가 변경되는 작업을 하더라도 리플로우를 방지할 수 있을 것입니다.
Pinterest는 어떤 방법을 사용하고 있을까?
다시 처음으로 돌아가서, Pinterest와 같은 레이아웃을 구성하기로 했을 때, Pinterest가 어떻게 구현했는지 살펴보는 게 좋을 것입니다.

Pinterest의 아이템 컨테이너는 relative로 구성되어 있고, 하위의 아이템들은 flat하게 absolute 아이템들로 이루어진 것을 확인할 수 있습니다.

단일 Item의 스타일 속성을 봤을 때, left, top을 0으로 고정하고 기준점으로 잡은 뒤, translate X, Y 속성을 통해 리플로우를 방지하는 방식으로 구현되어 있는 것을 확인할 수 있습니다.
요약
1. Pinterest는 무한 스크롤 로드와 가상 스크롤을 사용하고 있다.
2. 아이템 컨테이너는 relative로 구성되어 있다.
3. 하위 아이템은 flat하게 absolute 아이템으로 구성되어 있다
4. 아이템의 속성은 left, top 0으로 고정, translate X, Y 속성 조정을 통해 리플로우를 방지하고 있다.
라이브러리 이용하기
- react-masonry-css
과거부터 많이 사용되던 라이브러리이고, 지속적으로 개선되고 있는 라이브러리인 react-masonry-css는 CSS기반으로 동작하고 번들 사이즈도 가벼워 성능 부하가 가장 적은 선택이고, CSS기반이므로 TypeScript 프로젝트에도 적용하는 데 문제가 없습니다. 그런데 배치를 Pinterest 형태로 원한다면 무한스크롤을 위해 열 높이를 맞춰 주는 것이 필요한데, react-masonry-css는 이를 지원하지 않는 것을 고려해야 합니다.


하지만 여전히 인기 많은 라이브러리이다.
- react-responsive-masonry
masonry 배치를 지원하면서 flex 기반으로 동작하며 반응형도 지원하는 라이브러리입니다. 비교적 최근까지 업데이트되는 것으로 보이며, 번들 사이즈는 70kb정도로 masonry 배치 라이브러리중에서는 무거운 편입니다. 반응형도 정말 잘 되는 편이고, 궁금하다면 데모 페이지가 있으니 직접 들어가서 확인해보면 좋을 것 같습니다. npm 데모 이미지에서는 반응형으로 아이템 개수를 선택할 수 있는 것으로 보이는데, 실제 데모에서는 크기만 줄어듭니다.
react-responsive-masonry 2.3.0 Demo
cedricdelpoux.github.io
TypeScript 지원도 비교적 최근인 8개월 전 업데이트로 추가된 것으로 보입니다.

적용할 프로젝트의 속성에 따라 적용할 라이브러리를 선택하면 좋을 것 같습니다.
참고 자료
[MDN] https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Masonry_layout
Masonry layout - CSS | MDN
To create the most common masonry layout, your columns will be the grid axis and the rows the masonry axis, defined with grid-template-columns and grid-template-rows. The child elements of this container will now lay out item by item along the rows, as the
developer.mozilla.org
[WIT 블로그] https://wit.nts-corp.com/2022/10/26/6595
Masonry 레이아웃 구현하기 | WIT블로그
Masonry layout is a layout method where one axis uses a typical strict grid layout, most often columns, and the other a masonry layout.On the masonry axis, rather than sticking to a strict grid with gaps being left after shorter items, the items in the fol
wit.nts-corp.com
'React' 카테고리의 다른 글
| 프론트의 로깅은 무엇일까? 로깅을 알아보자 (0) | 2025.09.11 |
|---|---|
| [Zustand] Root 페이지의 오버레이 통제와 subscribeWithSelector (0) | 2025.08.25 |
| [Zustand] Zustand 기본 사용법, v5 변경점 (0) | 2025.08.20 |
| Presentational-Container 패턴과 Hook (1) | 2025.08.10 |
| [React] useRef 훅 정리 (0) | 2025.07.29 |