
위 이미지 처럼 콘텐츠 블록들을 벽돌처럼 빈 공간 없이 배치하는 레이아웃 방식으로, 대표적으로 핀터레스트를 생각하면 된다.
이 레이아웃을 만드려면 아래와 같은 방법이 있다.
grid-template-rows: masonry 속성도 있지만, 브라우저 호환성에 따라 사용이 제한된다.이왕 하는거 제대로 해보자는 생각이 들어 CSS Grid + 자바스크립트를 이용해 구현해보기로 했다.
이미지는 따로 저장하지 않고 랜덤으로 가져오는 방식을 택했다. picsum의 무료 이미지를 이용했는데, 아래와 같은 형식으로 사용하면 된다.
<img src="https://picsum.photos/${가로사이즈}/${세로사이즈}?random=${고유번호}" alt="" />
렌더링마다 이미지가 바뀌는 문제를 방지하기 위해, 초기 렌더링에서만 이미지 리스트가 고정되도록 useState를 사용했다.
const generateImageList = () => {
return Array.from({ length: 50 }, (_, index) => ({
num: index + 1, // 고유번호
height: Math.floor(Math.random() * 7 + 2), // 2~8사이의 숫자(높이값 백의자리)
}));
};
const Gallery = () => {
const [imgList] = useState(generateImageList()); //이미지 고정
return (
<ul className="gallery">
{imgList.map((item, index) => (
<li key={`gallery-img-list-${index}`} className="gallery__item">
<div>
<img src={`https://picsum.photos/500/${item.height}00?random=${item.num}`}
width="500" height={`${item.height}00`} alt="" />
</div>
</li>
))}
</ul>
);
};
.gallery {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
&__item {
img {
display: block;
width: 100%;
transition: var(--transition);
content-visibility: auto;
}
}
}

css만을 사용하면 위와 같은 레이아웃이 나온다. 이제 자바스크립트를 통해 각 아이템의 높이를 조정해보자.
이미지의 높이에 따라 Grid 아이템이 여러 개의 Grid row를 차지하도록 설정했다.
grid-auto-rows: 30px;Grid 참고 블로그
grid line에 대한 이해와grid-row속성 이용하는 방법을 알아보기 !
offsetHeight를 통해 각 아이템의 실제 높이를 구한다.
이미지의 실제 높이(offsetHeight),행의 높이와 행 간격을 고려해서 span의 값을 설정한다.
Grid 내부 각 item 높이 = 이미지태그.offsetHeight + 오차범위고려(rowGap)
Grid의 각 행 = 높이(rowHeight) + 행 간격(rowGap)
Math.floor()로 소수점 버림content-visibility: auto;contain-intrinsic-size: ${가로 세로} 설정requestAnimationFrame()useEffect(() => {
if (!gridRef.current) return; // gridRef가 없으면 함수 종료
const resizeGridItems = () => {
const grid = gridRef.current; // 그리드 요소 가져오기
if (!grid) return; // grid가 없으면 함수 종료
const items = grid.querySelectorAll(".gallery__item"); // 모든 그리드 아이템 선택
// CSS로 고정된 그리드의 각 행의 높이와 행 간격을 가져옴
const rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue("grid-auto-rows")) || 0;
const rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue("grid-row-gap"));
items.forEach((item) => {
// 각 아이템의 이미지 요소 찾기
const content = item.querySelector("img") as HTMLImageElement;
if (!content) return;
// 이미지 높이에 맞춰 몇 행(span)을 차지할지 계산
const rowSpan = Math.floor((content.offsetHeight + rowGap) / (rowHeight + rowGap));
// grid-row-end 속성으로 몇 행을 차지할지 설정
(item as HTMLElement).style.gridRowEnd = `span ${rowSpan}`;
});
};
requestAnimationFrame(resizeGridItems); // 브라우저가 렌더링을 완료한 후 함수 실행
}, [imgList]); // imgList가 변경될 때마다 실행

이미지 순서가 어떻게 배치되는지 확인하기 위해 오름순으로 숫자를 부여했다. 위와 같이 시작점이 높은것부터 순차적으로 배치된다.
예상했던 것처럼 스크립트 부분에서 시간을 많이 쏟았다. 특히 이미지 높이를 동적으로 측정하고, requestAnimationFrame을 활용해 불필요한 리렌더링을 최소화하려는 과정이 어려웠다. 그래도 성능 최적화를 위해 코드를 여러 번 수정하고 테스트하면서 최적화 기법에 대한 이해가 조금 생긴 것 같다.
이번 작업을 통해 CSS Grid를 다시 공부하게 되었는데, 실무에서는 flex만 주로 사용하다보니 매번 공부하는 느낌이지만 도움이 많이 됐다.😊 라이브러리에 의존하지 않고 직접 구현해보니 성취감이 큰 작업이었다.