본문으로 바로가기
  1. Home
  2. 그외/유용한 정보
  3. 자바스크립트로 반응형 벽돌 레이아웃 (Masonry Layout) 구현하기 방법

자바스크립트로 반응형 벽돌 레이아웃 (Masonry Layout) 구현하기 방법

· · 쓰윔

핀터레스트

 핀터레스트는 [**Masonry Layout**]을 사용하는 대표적인 사이트입니다.

 위의 사진을 보시면 알겠지만 [**Masonry Layout**]은 벽돌을 쌓듯이 썸네일을 차곡차곡 쌓아올린 레이아웃 형태를 뜻합니다.

 사진을 최적의 형태로 넣어 공간낭비를 최소화해서 사용자들에게 만족감을 주는 형태라고 보시면 됩니다.

 

Masonry Layout을 순수 자바스크립트로 구현하기

 

 인터넷에 찾아보면 JS 파일 형태의 플러그인이 제공되고 있습니다.

 하지만 플러그인은 용량도 크고 속도도 느릴 뿐더러, 간단한 코드로 구현가능하기 때문에 굳이 플러그인을 사용할 필요가 없다고 생각합니다.

 그래서 JAVASCRIPT를 이용해서 직접 만드는 방법을 알아볼까 합니다.

 

기본 원리

 

 기본적으로 HTML의 형태는 아래와 같습니다.

<div class="masonry">
  <div class="item"><img src="img01.jpg"></div>
  <div class="item"><img src="img02.jpg"></div>
  <div class="item"><img src="img03.jpg"></div>
  <div class="item"><img src="img04.jpg"></div>
  <div class="item"><img src="img05.jpg"></div>
  <div class="item"><img src="img06.jpg"></div>
</div>

 masonry라는 div로 전체를 감싸주고, item이라는 div안에 원하는 이미지를 넣어둔 상태입니다.

 

 CSS의 형태는 아래와 같습니다.

  * {
  margin: 0;
  padding: 0;
}



.masonry {
  width:100%;
  position:relative;
}

.item {
  width:33.3%;
  max-width:250px;
  display:inline-block;  
  position:absolute;

}

 masonry의 width는 레이아웃의 넓이입니다.

 item의 width는 최대 몇개의 행으로 구성할지를 정할 퍼센트값입니다. 예제에서 3줄의 행으로 구성할 예정이므로 33.3%를 사용했습니다.

 또한 한 줄에 최대 250px의 넓이를 줄 예정이므로 max-width를 250px로 지정했습니다.

 

 현재의 상태를 이해를 돕는 형태로 나타내면 아래와 같습니다.[각주*실제론 absolute로 해놓은 상태이기 때문에 겹쳐 있는 상태입니다.*]

기본원리1.jpg

 이제 이 상태에서 div.item을 3행에 차곡 차곡 쌓을 겁니다.

 이 때 하나의 행의 높이를 [0,0,0]이란 배열을 만들어서 측정할 겁니다.

기본원리2.jpg

 첫번째 아이템을 첫번째 행에 넣었습니다.

 이제 배열에 div.item의 높이를 감지해서 넣어줄 겁니다.

 그리고 배열 중에서 가장 높이가 낮은 배열로 아이템을 넣어줄 겁니다.

 

 만약 첫번째 div.item의 높이가 200px라면 배열은 [200,0,0]으로 변할테고 가장 낮은 두번째 배열로 두번째 아이템을 배치할 겁니다.

 그러면 배열은 [200,200,0]으로 변할테고, 이제 세번째 배열로 세번째 아이템을 배치할 겁니다.

 그러면 배열은 [200,200,200]으로 변할테지요.

 네번째 아이템은 [200,200,200]배열에서 가장 낮은 첫번째 배열로 아이템을 넣어줄테고, 그런식으로 계속 진행될 겁니다.

 

JAVSCRIPT로 제어하기

 

 제가 위에서 언급한 원리대로 아이템들을 배치해볼까 합니다.

 우선 이 아이템 배치시 고려해야할 점이 두 가지가 있습니다.

 

  • 사용자가 브라우저의 창을 축소하거나 늘리는 것을 감지해서 그때마다 다른결과를 내놔야 합니다.
  • 이미지가 로딩된 후에 높이가 정해져야 배치를 시작해야 합니다.

 따라서 함수를 발동할 때 addEventListener의 resize 이벤트와 DOMContentLoaded 이벤트를 사용해야 합니다.

 코드로 나타내면 이렇습니다.

document.addEventListener('DOMContentLoaded',function(){
  masonry_layout();
})
window.addEventListener('resize',function(){
  masonry_layout(); 
})
function masonry_layout () {
 // 코드 넣기
}

 이제 masonry_layout 함수에서 코드를 짜면 됩니다.

 코드는 아래와 같습니다.

document.addEventListener('DOMContentLoaded',function(){
  masonry_layout();
})
window.addEventListener('resize',function(){
  masonry_layout(); 
})
function masonry_layout () {
  const Masonry = document.querySelectorAll('.masonry'); //Masonry 아이템이 있는지 감지
  if (!Masonry) {return !1} // 없다면 함수종료
  Masonry.forEach(function(ele){ // 각 Masonry 아이템에 대해
   var imgMove = [0,0,0]; //이미지 높이를 저장할 배열
   const leftWidth = 260; // 아이템 한 행의 넓이 250px에 간격인 10을 더한 상태
  
   const item = ele.getElementsByClassName('item'); // 각 Masonry아이템에 있는 item을 획득
    for (var i=0; i<item.length; i++){ // 각 item에 대해
       const min = imgMove.indexOf(Math.min.apply(0, imgMove)); // imgMove의 배열의 최소값이 있는 인덱스 숫자 획득
      const x = leftWidth * min; // 한행의 넓이 x 인덱스 숫자로 x 좌표 만들기
      const itemHeight = item[i].offsetHeight; // 아이템의 높이를 구하기
  const y = imgMove[min] // imgMove의 최소값이 있는 배열에 있는 y값을 불러오기
      imgMove[min] += itemHeight+20; // 최소값의 배열에 아이템의 높이 저장하기. 20은 간격이다.
     item[i].setAttribute('style','transform:translate('+x+'px,'+y+'px)') // 아이템의 x,y 좌표를 transform으로 지정해주기
    }

    const imgMax = Math.max.apply(0, imgMove); // 최종적으로 아이템배열 최대값 높이를 가져오기
    ele.setAttribute('style','height:'+imgMax+'px') // 감싸고 있는 masonry의 높이를 지정해주기
  })  

}

 주석을 달아 설명했지만 약간의 이해가 있어야될 것 같아서 그림을 준비했습니다.

Masonry_원리3.jpg

 여기에서 핵심은 Math.min과 indexOf입니다.

 이 두가지를 조합하면 최소값을 가진 배열의 인덱스값을 알아낼 수 있습니다.

 그 인덱스값에 x좌표의 넓이로 사용할 기본값을 곱하면 딱 맞게 x좌표를 지정할 수 있습니다.

 

 그리고 다음번에 그 위치에 아이템을 넣으려면 처음으로 넣은 아이템만큼의 높이만큼 y좌표를 줘야하므로 배열에 imgMove[min] += itemHeight+20[각주*20은 간격*]을 주는 것이지요.

 

 하지만 이 코드에서 단점이 있는데, masonry는 자식들이 absolute다보니 기본 높이가 0으로 나옵니다.

 그러면 다른 레이아웃과 겹치게 되는 불상사가 일어나게 되는데, 배열에 있는 가장 긴 높이를 Math.max로 가져와서 부모의 높이로 지정하는 겁니다.

 

반응형으로 다듬기  

 

 이제 이 코드를 적용하면 Masonry 레이아웃이 완성되게 됩니다.

 하지만 아직까지 반응형은 아닙니다.

 모바일로 이 화면을 볼 때엔 아이템들이 밖으로 삐져나오게 되면서 잘리기 때문입니다.

 

 이건 약간의 css와 javascript 작업으로 수정 가능합니다.

 

 우선 반응형으로 만들기 위해서 790px 이하의 작은화면에선 3행이던걸 2행으로 바꿀겁니다.

 그리고 520px 이하의 작은 화면에선 2행이던걸 1행으로 바꿀 겁니다.

 

 먼저 css로 아이템을 설정할 필요가 있습니다.

@media (max-width:790px) {
  .item {
    width:calc(50% - 10px);
    max-width:unset;
  } 
}
@media (max-width:520px) {
  .item{
    width:100%;
    max-width:unset;
  }
}

 .item의 밑에 위와 같은 코드를 넣으면 790px부턴 아이템이 2개의 행에 걸쳐서 나올 것이고, 520px부턴 아이템이 1개의 행에 걸쳐서 나올 겁니다.

 하지만 여전히 자바스크립트에선 처리가 되지 않았기 때문에, 여기선 if문을 이용해서 화면의 크기에 따라 분기를 줄 겁니다.

 const body = document.querySelector('body');
if (body.clientWidth >= 790) {
   var imgMove = [0,0,0]; //이미지 높이를 저장할 배열
   var leftWidth = 260; // 아이템 한 행의 넓이 250px에 간격인 10을 더한 상태
   }
   else if (body.clientWidth >=520){
     var imgMove = [0,0];
       var leftWidth = body.clientWidth/2; // 아이템 한 행의 전체 넓이 나누기 2를 한 상태
   }
    else {
      var imgMove= [0]
      var leftWidth = body.clientWidth; // 아이템 한 행에 전체 넓이를 대입
    }

 방법은 위와 같습니다.

 

완성품

 

 위에서 다룬 방법대로 진행해서 완성품 실제 예제를 만들어봤습니다.

 직접 화면을 늘리거나 줄이시면 반응형으로 Masonry가 작동하는 걸 보실 수 있을 겁니다.

 

 

관련글

무한 스크롤 (Infinite Scroll) 구현하기

캐러셀 슬라이드 구현하기

스크롤을 내리면 상단에 고정되는 네비 메뉴 만들기

간편 메모장
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.

복사하기  저장
× 내용 모두 지우기 내용 모두 복사하기
메모장입니다. 시크릿 모드가 아니면 내용이 저장됩니다.