SeonHyungJo / Tip-Note

:round_pushpin: 개발을 하면서 느끼고 알게된 Tip:round_pushpin:
7 stars 0 forks source link

Masonry Layout(CSS Grid + Intersection Observer + insertAdjacentElement) == Pinterest Layout #41

Open SeonHyungJo opened 5 years ago

SeonHyungJo commented 5 years ago

오늘은 몇시간만에 겨우 만든 Masonry Layout를 소개하려고 합니다. 흔히 사람들이 핀터레스트 레이아웃이라고 알고 있는 것으로 스크롤이 끝에 닿으면 자동으로 사진들이 더 보여주는 기능입니다.

하단에 보이는 노란색 Div에 Intersection Observer를 걸어서 무한스크롤을 구현했으며 Grid Cell을 추가하는데 성능을 고려하여 insertAdjacentElement()를 사용하였습니다.

무엇보다 CSS3 Grid Layout을 사용하여 반응형에도 좋으며 구현하는데 도움이 많이 되었습니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="./index.css">
    <script src="./index.js"></script>
    <title>Document</title>
</head>

<body>
    <div id="gridWrapper"></div>
</body>
<script>
    const INIT_COLUMN_SIZE = 5; // Grid Template Size
    const INIT_ROW_SIZE = 20; // Grid Template Size
    const MAX_CELL_COLUMN_SIZE = 3; // Grid Cell Size
    const MAX_CELL_ROW_SIZE = 10; // Grid Cell Size
    const ADD_CELL_SIZE = 20; // Create Cell Count

    // Init currentRowNumList
    const currentRowNumList = Array(INIT_COLUMN_SIZE).fill(1);

    // Create Wrapper Div
    const gridWrapper = document.querySelector("#gridWrapper");

    // Create masonry Div
    const masonry = document.createElement("div");
    masonry.id = "masonry";
    masonry.style.gridTemplateColumns = `repeat(${INIT_COLUMN_SIZE}, 1fr)`;
    masonry.style.gridTemplateRows = `repeat(${INIT_ROW_SIZE}, 1fr)`;

    // Create scrollPoint Div
    const scrollPoint = document.createElement("div");
    scrollPoint.id = "scrollPoint";

    gridWrapper.insertAdjacentElement("afterbegin", masonry);
    gridWrapper.insertAdjacentElement("afterend", scrollPoint);

    // Make intersectionObserver to create add new grid cells
    const intersectionObserver = new IntersectionObserver(function (entries) {
        Promise.all(createGridCell(ADD_CELL_SIZE)).then(() => {
            console.log("Finish");
        })
    });

    // start observing
    intersectionObserver.observe(document.querySelector("#scrollPoint"));
</script>

</html>

index.js

let standardColumnRect = 1;
let standardRowRect = 1;

const getRandomNum = maxNum => {
  return Math.ceil(Math.random() * maxNum);
};

const addGridAction = () =>
  new Promise(resolve => {
    let maxColSize = 1;

    for (let i = standardColumnRect; i < INIT_COLUMN_SIZE; i++) {
      if (currentRowNumList[i] > standardRowRect) {
        break;
      }
      maxColSize += 1;
    }

    const columnSizeCondition =
      maxColSize >= MAX_CELL_COLUMN_SIZE ? MAX_CELL_COLUMN_SIZE : maxColSize;
    const columnSize = getRandomNum(columnSizeCondition);
    const rowSize = Math.ceil(Math.random() * MAX_CELL_ROW_SIZE + 1);

    // Random Picture from https://source.unsplash.com/random
    const newDiv = document.createElement("div");
    newDiv.style.gridColumn = `${standardColumnRect} / ${standardColumnRect +
      columnSize}`;
    newDiv.style.gridRow = `${standardRowRect} / ${standardRowRect + rowSize}`;
    newDiv.style.backgroundImage = "url('https://source.unsplash.com/random')";
    newDiv.style.backgroundSize = "cover";
    newDiv.style.backgroundRepeat = "no-repeat";
    newDiv.style.border = "1px solid black";

    masonry.insertAdjacentElement("beforeend", newDiv);

    for (let i = standardColumnRect; i < standardColumnRect + columnSize; i++) {
      currentRowNumList[i - 1] += rowSize;
    }

    // Grid 우측 영역을 넘어가면 1로 초기화
    standardRowRect = Math.min(...currentRowNumList);
    standardColumnRect = currentRowNumList.indexOf(standardRowRect) + 1;

    resolve();
  });

const createGridCell = cellCount => {
  const promiseList = [];

  for (let i = 0; i < ADD_CELL_SIZE; i++) {
    promiseList.push(addGridAction());
  }

  return promiseList;
};

index.css

body{
    margin: 0px;
    padding: 10px;
}

#masonry {
    display: grid;
    border: 3px solid black;
    gap: 20px;

    min-height: 120vh;
    margin: 0px;
 }

 #scrollPoint{
     width: 100px;
     height: 100px;
     border: 1px solid yellow;
     background-color: yellow;
 }

시연

Masonry