관련지식
html, css, javascript, masonry layout, grid layout

핀터레스트와 같은 레이아웃을 Masonry Layout 이라고 합니다. Grid Layout은 영역을 고정하기 때문에 컨텐츠의 크기가 들쭉날쭉할 경우 어떤 부분은 꽉차 보이지만 어떤 부분은 휑하게 비어 보입니다. Masonry Layout 은 영역이 고정되어있지 않고 컨텐츠의 크기에 따라 배치하기 때문에 빈 영역이 최소화 됩니다. 특히 각 컨텐츠의 가로 사이즈는 고정되고 세로 사이즈만 다르다면 Masonry Layout 은 완벽할 정도의 컨텐츠를 배치합니다.
Masonry 라이브러리를 이용하여 만들어 보겠습니다.

경로 : https://masonry.desandro.com/
설치 : npm, bower, CDN 등
현재버전 : 4.2.2

샘플로 사용할 html 은 아래와 같습니다. 이미지는 제가 블로그에서 사용하기 위해 이전에 업로드했던 것들입니다.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title></title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
  8. </head>
  9. <body>
  10. <img src="https://i.postimg.cc/gkQkrcf3/4.png" />
  11. <img src="https://i.postimg.cc/zvyyDzqP/5.png" />
  12. <img src="https://i.postimg.cc/BZz3Jy9S/custom1.png" />
  13. <img src="https://i.postimg.cc/XYTgXByp/calendar1.png" />
  14. <img src="https://i.postimg.cc/T1jKdLr8/design2.png" />
  15. <img src="https://i.postimg.cc/Hs2pxzXv/button1.png" />
  16. <img src="https://i.postimg.cc/xTwC6ChB/part2.png" />
  17. <img src="https://i.postimg.cc/9Qc7S16s/canvas1.png" />
  18. <img src="https://i.postimg.cc/ncT3N5BM/extapp1.png" />
  19. <img src="https://i.postimg.cc/D0XZbr4D/scrolllock1.jpg" />
  20. <img src="https://i.postimg.cc/NFdy16gk/smsbackup3.jpg" />
  21. <img src="https://i.postimg.cc/vHc0VhBL/cisco3.jpg" />
  22. </body>
  23. </html>

기본 설정

Masonry 레이아웃을 적용하기 위해서는 이미지 태그를 <div>로 한번 감싸야 합니다. 클래스 이름은 무엇으로 지정해도 상관없지만, 공식 가이드와 동일하게 gridgrid-item 으로 하겠습니다.

  1. <div class="grid">
  2. <div class="grid-item"><img src="https://i.postimg.cc/gkQkrcf3/4.png" /></div>
  3. <div class="grid-item"><img src="https://i.postimg.cc/zvyyDzqP/5.png" /></div>
  4. <div class="grid-item"><img src="https://i.postimg.cc/BZz3Jy9S/custom1.png" /></div>
  5. <div class="grid-item"><img src="https://i.postimg.cc/XYTgXByp/calendar1.png" /></div>
  6. <div class="grid-item"><img src="https://i.postimg.cc/T1jKdLr8/design2.png" /></div>
  7. <div class="grid-item"><img src="https://i.postimg.cc/Hs2pxzXv/button1.png" /></div>
  8. <div class="grid-item"><img src="https://i.postimg.cc/xTwC6ChB/part2.png" /></div>
  9. <div class="grid-item"><img src="https://i.postimg.cc/9Qc7S16s/canvas1.png" /></div>
  10. <div class="grid-item"><img src="https://i.postimg.cc/ncT3N5BM/extapp1.png" /></div>
  11. <div class="grid-item"><img src="https://i.postimg.cc/D0XZbr4D/scrolllock1.jpg" /></div>
  12. <div class="grid-item"><img src="https://i.postimg.cc/NFdy16gk/smsbackup3.jpg" /></div>
  13. <div class="grid-item"><img src="https://i.postimg.cc/vHc0VhBL/cisco3.jpg" /></div>
  14. </div>

그리고 Masonry 라이브러리를 호출합니다. JQuery의 플러그인 형태로도 사용이 가능하지만 여기서는 바닐라 JS로 작성해보겠습니다.

  1. var msnry = new Masonry( '.grid', {
  2. itemSelector: '.grid-item',
  3. columnWidth: 200
  4. });

CSS를 작성하지 않으면 라이브러리를 호출해도 적용되지 않습니다. 가장 단순하게 하나의 효과만 적용하겠습니다.

  1. .grid-item img {display: block; min-width:100%; max-width: 100%;}

처음 이미지를 봤을땐 적용이 안된것처럼 보이지만, 스크롤을 조금 내리면 적용이 된것을 볼수 있습니다. 이것이 가장 기본적인 형태의 레이아웃입니다.

가로 사이즈 고정

핀터레스트의 경우 이미지의 세로길이는 다르지만 가로길이는 전부 통일되어있습니다. 각 이미지의 크기가 너무 제각각 다르면 가로길이는 고정하는 것이 더 보기 좋습니다. 따라서 css 를 하나 더 추가하겠습니다.

  1. .grid-sizer, .grid-item { width: 200px; }

굉장히 깔끔해졌습니다!

가로 사이즈 퍼센트(%) 적용

현재 상태에서도 브라우저 크기를 조정하면 그에 맞춰 이미지의 위치가 다시 정렬됩니다. 하지만 각 이미지의 가로 사이즈는 200픽셀로 고정이 된 상태죠. 디바이스 해상도에 상관없이 동일한 레이아웃이 보여지도록 가로 사이즈를 % 로 바꿔보겠습니다.

먼저 grid-sizer 를 추가합니다. 이 객체는 가로 사이즈의 기준점이 됩니다.

  1. <div class="grid">
  2. <div class="grid-sizer"></div> <!-- 추가 -->
  3. <div class="grid-item"><img src="https://i.postimg.cc/gkQkrcf3/4.png" /></div>

css 에서 width 를 %값으로 적용합니다.

  1. .grid-sizer, .grid-item { width: 25%; }
  2. /* .grid-sizer, .grid-item { width: 200px; } */

스크립트에서 columnWidth 값을 grid-sizer 클래스명으로 바꾸고 percentPosition 항목을 true로 세팅합니다.

  1. // columnWidth: 200
  2. columnWidth: '.grid-sizer',
  3. percentPosition: true,

이제 화면을 테스트하면 브라우저 크기를 바꾸더라도 4열 레이아웃을 유지하는것을 보실수 있습니다.

여백 적용

테스트 이미지로는 구분이 잘 안가지만, 현재 이미지와 이미지 사이엔 공백없이 붙어있습니다. 이미지와 이미지에 여백을 넣으면 핀터레스트와 더욱 비슷할것 같습니다. css로 적용할수도 있지만 Masonry 에 gutter 옵션을 추가하면 간단하게 처리됩니다.

  1. percentPosition: true,
  2. gutter : 20, //추가

적용이 잘 되었는지 확인하기 위해 grid-item에 테두리를 그려주겠습니다.

  1. .grid-item {border:1px solid #8f99f3; }

여백은 적용되었지만 4열이 아니라 3열로 보일겁니다. 가로사이즈 25% 에 gutter와 border가 추가되었기 때문입니다. 따라서 가로크기를 23%로 변경하겠습니다.

  1. .grid-sizer, .grid-item { width: 23%; } /* 수정 */

gutter 덕분에 열과 열 사이에는 공백이 충분히 생겼습니다. 그런데 이미지의 상하 간격은 너무 좁아 보이네요. 이것은 margin-bottom 속성을 추가하면 됩니다.

  1. .grid-item {border:1px solid #8f99f3; margin-bottom:15px;}

unloaded image 처리

호출이 안되는 이미지가 있을 경우 이미지와 이미지가 겹치는 증상이 생길수 있습니다. 그럴땐 imagesLoaded 라이브러리를 이용할수 있습니다.

경로 : https://imagesloaded.desandro.com/

먼저 스크립트 로딩을 추가합니다.

  1. <script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
  2. <script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script> <!-- 추가 -->

그리고 Masonry 생성로직 이후에 다음과 같은 스크립트를 추가하면 됩니다.

  1. imagesLoaded( '.grid' ).on( 'progress', function() {
  2. msnry.layout();
  3. });

imagesLoaded 에는 총 4개의 이벤트가 있습니다.

  • always : 모든 이미지가 로딩되고 깨진 이미지 링크 확인 후 호출
  • done : 깨진 이미지 링크 없이 모두 로딩 되었을때 호출
  • fail : 최소 한개 이상의 이미지 링크가 깨졌을때 호출
  • progress : 각 이미지가 로딩될때마다 호출

따라서 progress 이벤트를 사용할 경우엔 디바운싱이나 쓰로틀링을 적용해야 브라우저의 부하를 줄일수 있을것 입니다.

마무리

핀터레스트처럼 라운드 효과를 주고 싶다면 css를 더 추가하면 되겠죠? border-radius 속성과 overflow 속성을 추가했습니다.

  1. .grid-item {border:1px solid #8f99f3; margin-bottom:15px; border-radius: 20px; overflow:hidden;}

각 grid-item 은 반드시 이미지가 아니여도 됩니다. grid layout 같은 레이아웃 개념이기 때문에 html을 구성하는 모든 객체를 넣는것이 가능합니다.
여기서 다룬 기본적인 옵션만으로도 상당히 괜찮은 Masonry Layout을 만들수 있지만 그외 다른 옵션과 이벤트도 있으므로 새로운 구현이 필요하신 분은 홈페이지에서 API를 확인해보시는게 좋겠습니다.

최종 소스

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title></title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
  8. <script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
  9. <style>
  10. .grid { width:80% }
  11. .grid-sizer, .grid-item { width: 23%; }
  12. /* .grid-sizer, .grid-item { width: 200px; } */
  13. .grid-item {border:1px solid #8f99f3; margin-bottom:15px; border-radius: 20px; overflow:hidden;}
  14. .grid-item img {display: block; min-width:100%; max-width: 100%; }
  15. </style>
  16. </head>
  17. <body>
  18. <div class="grid">
  19. <div class="grid-sizer"></div>
  20. <div class="grid-item"><img src="https://i.postimg.cc/gkQkrcf3/4.png" /></div>
  21. <div class="grid-item"><img src="https://i.postimg.cc/zvyyDzqP/5.png" /></div>
  22. <div class="grid-item"><img src="https://i.postimg.cc/BZz3Jy9S/custom1.png" /></div>
  23. <div class="grid-item"><img src="https://i.postimg.cc/XYTgXByp/calendar1.png" /></div>
  24. <div class="grid-item"><img src="https://i.postimg.cc/T1jKdLr8/design2.png" /></div>
  25. <div class="grid-item"><img src="https://i.postimg.cc/Hs2pxzXv/button1.png" /></div>
  26. <div class="grid-item"><img src="https://i.postimg.cc/xTwC6ChB/part2.png" /></div>
  27. <div class="grid-item"><img src="https://i.postimg.cc/9Qc7S16s/canvas1.png" /></div>
  28. <div class="grid-item"><img src="https://i.postimg.cc/ncT3N5BM/extapp1.png" /></div>
  29. <div class="grid-item"><img src="https://i.postimg.cc/D0XZbr4D/scrolllock1.jpg" /></div>
  30. <div class="grid-item"><img src="https://i.postimg.cc/NFdy16gk/smsbackup3.jpg" /></div>
  31. <div class="grid-item"><img src="https://i.postimg.cc/vHc0VhBL/cisco3.jpg" /></div>
  32. </div>
  33. <script>
  34. var msnry = new Masonry( '.grid', {
  35. itemSelector: '.grid-item',
  36. // columnWidth: 200
  37. columnWidth: '.grid-sizer',
  38. percentPosition: true,
  39. gutter : 20,
  40. });
  41. imagesLoaded( '.grid' ).on( 'progress', function() {
  42. msnry.layout();
  43. });
  44. </script>
  45. </body>
  46. </html>