CSS&JS/👀Study and Copy

[JS] vanilla JS로 Masonry Layout 구현하기(1)

arancia_ 2023. 1. 25. 13:19

 

See the Pen Masonry Layout by Oh Ikmyeong (@dpffpself) on CodePen.

한 2018년 쯤에 jQuery 라이브러리 없이 어떻게 하면 Masonry Layout을 구축할 수 있을까... 고민했었다. 이 블로그 해당 카테고리의 가장 초반 글도 그거랑 관련된 내용일꺼다... 그 때 당시엔 지금보다 훠어얼씬 더 자바스크립트를 못하고 (제이쿼리만 할 줄 알았음) 문제해결능력도 엄청 낮았기에 css의 column 속성으로 하드코딩 했었다.

이건 JS와 position:absolute를 이용하여 강제적으로 포지션값을 변경하는 방식이다.
장점은 애니메이션 효과가 적용됨 (transition)
단점은 짝수개의 컬럼일때 이미지 길이에 따라 한쪽만 길게 늘어지는 것 처럼 보이는 현상이 발생할 수도 있다.

  • 각 이미지를 감싼 DOM에 position:absolute와 left, top값을 계산하여 넣는다
  • 가장 밑에 있는 이미지의 top값 + 자체 높이값을 더한 값을 부모 wrap의 height 값으로 넣어준다

 

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Masonry Layout</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js" defer></script>
</head>
<body>
    <h1 style="text-align:center;font-size:10vmin; background:#000; color:#fff; padding:1em;">Masonry Layout</h1>
    <div id="sample-wrap"></div>
    <footer style="text-align:center;font-size:10vmin;background:#ccc;">I'm Footer</footer>
</body>
</html>
cs

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@charset "utf-8";
*{margin:0;padding:0;box-sizing:border-box;}
img{vertical-align:middle;}
/* wrap */
.msr-wrap{
    position:relative;
    width:100%; max-width:900px;
    margin:20px auto;
}
 
/* item */
.msr-item{
    position:absolute;
    top:0; left:0;
    overflow:hidden;
    transition:all .3s;
}
 
/* img */
.msr-item img{
    width:100%; height:auto;
}
cs

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class MasonryLayoutBuilder{
    set_wrap(selector){
        if(selector){
            this.$parent = document.querySelector(selector);
        }else{
            this.$parent = document.createElement('SECTION');
            document.body.appendChild(this.$parent);
        }
        return this;
    }
 
    set_item(itemList){
        this.itemList = itemList;
        return this;
    }
 
    init(){
        new MasonryLayout(this);
    }
}//class-MasonryLayoutBuilder
 
class MasonryLayout{
    constructor(BUILDER){
        this.$parent = BUILDER?.$parent;
        this.itemList = BUILDER?.itemList;
        this.$wrap = null;
        this.init();
    }//constructor
 
    init(){
        this.make_wrap();
        this.fill_wrap();
        this.$parent && this.$parent.appendChild(this.$wrap);
        this.adjust_size();
 
        window.addEventListener('resize',this.adjust_size);
    }//init
 
    make_wrap(){
        this.$wrap = document.createElement('UL');
        this.$wrap.classList.add('msr-wrap');
    }//make_wrap
 
    fill_wrap(){
        if(!this.itemList) return;
        const $frag = document.createDocumentFragment();
        this.itemList.forEach((src,idx)=>{
            const $item = document.createElement('LI');
            const $img = new Image();
            $item.classList.add('msr-item');
            $img.src = src;
            $img.title = idx;
            $item.appendChild($img);
            $frag.appendChild($item);
        });
        this.$wrap.appendChild($frag);
    }//fill_wrap
 
    adjust_size = ()=>{
        setTimeout(()=>{
            this.set_pos_item();
            this.set_wrap_height();
        },1000);
    }//adjust_size
 
    set_pos_item(){
        const $$item = Array.from(this.$wrap.children);
        const winWid = window.innerWidth;
        const GAP = winWid >= 600 ? 20 : 0;
        const COL = winWid >= 1400 ? 3 : (winWid >= 600 ? 2 : 1);
        const wid = (this.$wrap.offsetWidth / COL) - GAP + (GAP / COL);
 
        $$item.forEach(($item,idx)=>{
            $item.style.width = `${wid}px`;
            const perX = idx % COL;
            const left = (perX * wid) + (perX * GAP);
 
            $item.style.left = `${left}px`
            $item.style.top = `${this.get_prev_height($$item,COL,idx)}px`;
        });
    }//set_pos_item
 
    get_prev_height($$item,COL,idx){
        if(idx < COL) return 0;
        let totalTop = 0;
        for(let i=idx-COL; i>=0; i-=COL){
            const $prevItem = $$item[i];
            totalTop += ($prevItem.getBoundingClientRect().height) + 20;
        }
        return totalTop;
    }//get_prev_height
 
    set_wrap_height(){
        const $$item = Array.from(this.$wrap.children);
        let maxBottom = 0;
        let $lastItem = null;
        $$item.forEach($item =>{
            const top = parseInt($item.style.top);
            if(maxBottom <= top){
                maxBottom = top;
                $lastItem = $item;
            }
        })
 
        this.$wrap.style.height = `${maxBottom + $lastItem.getBoundingClientRect().height + 20}px`;
    }//set_wrap_height
}//class-MasonryLayout
 
/* ================= 실행 ================= */
const imgList = [
    "https://images.unsplash.com/photo-1674560109079-0b1cd708cc2d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80",
    "https://images.unsplash.com/photo-1674318012388-141651b08a51?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1932&q=80",
    "https://images.unsplash.com/photo-1672661164570-d5e7e0890a69?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80",
    "https://images.unsplash.com/photo-1673993599169-32f4b7e8cbd4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
    "https://images.unsplash.com/photo-1603379507360-d80df166fe4e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=732&q=80",
    "https://images.unsplash.com/photo-1639182946622-7de9d7efa6b4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80",
    "https://images.unsplash.com/photo-1597589827317-4c6d6e0a90bd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1160&q=80",
    "https://plus.unsplash.com/premium_photo-1663954864809-4279479735bd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
    "https://images.unsplash.com/photo-1644152231863-e3ae720cc399?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=688&q=80",
    "https://images.unsplash.com/photo-1673941733064-2445362593ce?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
    "https://images.unsplash.com/photo-1621383569754-5a8744581af3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=633&q=80",
    "https://plus.unsplash.com/premium_photo-1661603771539-faa0e3ed7ca6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
    "https://images.unsplash.com/photo-1674503431654-885753f78ee4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80",
    "https://images.unsplash.com/photo-1674421338840-de8c149e8c19?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1511&q=80"
]
new MasonryLayoutBuilder()
.set_wrap('#sample-wrap')
.set_item(imgList)
.init();
cs