CSS&JS/👀Study and Copy

[CSS+JS] 3분할 슬라이드(1) - 초단순 버젼

arancia_ 2023. 1. 18. 17:02

저 분의 코드펜을 보고 내 식대로 바닐라 자바스크립트로 짜보았음.

처음엔 background로 접근했다가 사이즈별 대응이 잘 안되어서 좀 무겁더라도 ㅎ..코드는 간단해지는 쪽으로 변경함.
이미지 객체의 사이즈를 100vw 100vh로 한 다음 object-fit:cover로 해주면 된다. 그리고 position:absolute;에 left값을 -3, -6 으로 바꿔주면 된다.

맨 처음에서 이전 버튼을 누를시에는 매끄럽진 않음..근데 귀찮다 그냥... 

HTML

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
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js" type="module"></script>
</head>
<body>
    <!-- <a href="https://codepen.io/theseventh/pen/LYVqMYb">Yet another slider by Arseny M.</a> -->
    <template>
        <div id="wrap-slide">
            <ul id="slide">
                <li class="slide-item">
                    <div class="slide-item-left"></div>
                    <div class="slide-item-center"></div>
                    <div class="slide-item-right"></div>
                </li>
                <li class="slide-item">
                    <div class="slide-item-left"></div>
                    <div class="slide-item-center"></div>
                    <div class="slide-item-right"></div>
                </li>
            </ul>
            <div id="slide-btns">
                <button class="slide-btn-prev"></button>
                <button class="slide-btn-next"></button>
            </div>
        </div>
    </template>
</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
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
@charset "utf-8";
*{margin:0;padding:0;box-sizing:border-box;}
li{list-style-type:none;}
 
/* 슬라이드 전체 묶는 wrap */
#wrap-slide{
    position:relative;
    width:100%;height:100vh;
}
 
/* 슬라이드 */
#slide{
    position:relative;
    width:100%; height:100%;
    cursor:ns-resize;
}
 
/* 슬라이드 개별 아이템 */
.slide-item{
    --wid: calc(100% / 3);
    position:absolute; overflow:hidden;
    top:0;left:0;
    width:100%; height:100vh;
}
[class ^="slide-item-"]{
    position:absolute; overflow:hidden;
    top:0;
    width:var(--wid); height:100%;
    /* border:1px solid rgba(28, 32, 53, 0.2); */
    /* border-width:0 1px; */
    pointer-events:none;
}
 
/* 슬라이드 개별 아이템 속 3분할 */
.slide-item-left{
    left:0;
    transition:transform 1s .2s cubic-bezier(0.68, 0.03, 0.29, 1.09);
}
.slide-item-center{
    left:var(--wid);
    transition:transform 2s 0s cubic-bezier(0.68, 0.03, 0.29, 1.09);
}
.slide-item-right{
    right:0;
    transition:transform 1s .2s cubic-bezier(0.68, 0.03, 0.29, 1.09);
}
 
/* 3분할 속 이미지 */
[class ^="slide-item-"] img{
    position:absolute;
    top:0;left:0;
    width:100vmax; height:100vh; object-fit:cover;
}
 
.slide-item-left img{
    left:0;
}
.slide-item-center img{
    left:calc(var(--wid) * -3);
}
.slide-item-right img{
    left:calc(var(--wid) * -6);
}
 
/* 이전 다음 버튼 */
#slide-btns{
    position:absolute;
    top:50%; right:20px;
    transform:translateY(-50%);
}
 
[class ^="slide-btn-"]{
    display:block;
    width:50px; aspect-ratio:1/1;
    margin:30px 0;
    background:rgba(0,0,0,.1);
    border:1px solid rgba(255,255,255,.5);
    border-radius:50%;
    filter:saturate(5);
    backdrop-filter:blur(10px);
    cursor:pointer;
}
[class ^="slide-btn-"]::after{
    content:'';display:block;position:absolute; box-sizing:border-box;
    left:50%; transform:translateX(-50%) rotate(45deg);
    width:12px;aspect-ratio:1/1;
    border:1px solid #fff;
    pointer-events:none;
}
.slide-btn-prev::after{
    top:45%;
    border-width:1px 0 0 1px;
}
.slide-btn-next::after{
    top:33%;
    border-width:0 1px 1px 0;
}
 
/* 슬라이드 타이틀 텍스트 */
#slide-title{
    position:absolute;
    top:50%;left:50%;
    transform:translate(-50%,-50%);
    text-align:center;
    font-size:10vmin; color:#fff;
    mix-blend-mode:overlay;
    pointer-events:none; user-select:none;
}
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
class SliderBuilder{
    set_images(images){
        this.images = images;
        return this;
    }
 
    set_title(title){
        this.title = title;
        return this;
    }
 
    init(){
        if(!this.images) return console.error('이미지 리스트 넣어라...');
        return new YetAnotherSlide(this).init();
    }
}//class-SliderBuilder
 
class YetAnotherSlide{
    constructor(Builder){
        this.images = Builder.images;
        this.title = Builder?.title;
        this.$wrap = null;
        this.$slide = null;
        this.$btns = null;
 
        this.POS = {prev:null, now:null};
        this.idxCurrSlide = 0;
        this.maxLen = Builder.images.length - 1;
 
        this.flagMove = true;
    }//constructor
 
    init(){
        //DOM 추가
        this.make_wrap();
        this.make_slide();
        this.set_move_slide();
        this.make_btns();
 
        this.$wrap.appendChild(this.$slide);
        this.$wrap.appendChild(this.$btns);
        document.body.appendChild(this.$wrap);
        this.make_title();
 
        //EVENT 추가
        this.add_on_mousedown_event();
        this.add_btn_event();
    }//init
 
    /**
     * 전체 감싸는 wrap 추가
     */
    make_wrap(){
        this.$wrap = document.createElement('DIV');
        this.$wrap.id = 'wrap-slide';
    }
 
    /**
     * 슬라이드 추가
     */
    make_slide(){
        this.$slide = document.createElement('UL');
        this.$slide.id = 'slide';
        const $frag = document.createDocumentFragment();
        const posList = ["left","center","right"];
        
        for(let i=this.maxLen; i>=0; i--){
            const $li = document.createElement('LI');
            const url = this.images[i];
            $li.classList.add('slide-item');
            posList.forEach(pos => {
                const $div = document.createElement('DIV');
                $div.classList.add(`slide-item-${pos}`);
                const $img = new Image();
                $img.src = url;
                $div.appendChild($img);
                $li.appendChild($div);
            });
            $frag.appendChild($li);
        }//for
 
        this.$slide.appendChild($frag);
    }//make_slide
 
    /**
     * 이전 다음 버튼 추가
     */
    make_btns(){
        this.$btns = document.createElement('DIV');
        this.$btns.id = 'slide-btns';
        const btnList = ["prev","next"];
        btnList.forEach(dir => {
            const $btn = document.createElement('BUTTON');
            $btn.classList.add(`slide-btn-${dir}`);
            this.$btns.appendChild($btn);
        });
    }//make_btns
 
    /**
     * 슬라이드 텍스트  추가
     */
    make_title(){
        if(!this.title) return;
        const $h1 = document.createElement('H1');
        $h1.id = 'slide-title';
        $h1.textContent = this.title;
        this.$wrap.appendChild($h1);
    }//make_title
 
    /** mousedown시 이벤트 추가 */
    add_on_mousedown_event(){
        this.$slide.addEventListener('mousedown',this.on_down, {once:true});
    }
 
    /** 
     * mousedown시 이벤트 
     * 마우스 다운 당시의 포지션y값을 저장
     * 마우스를 뗐을 때의 이벤트를 추가한다.
     * */
    on_down = (e) =>{
        this.POS.prev = e.clientY;
        this.$slide.addEventListener('mouseup',this.on_up, {once:true});
    }//on_down
 
    /** 
     * mouseup시 이벤트
     * 마우스를 뗐을때의 포지션 y값을 저장
     * 슬라이드를 이동시키고
     * 다시 mousedown이벤트를 추가해준다.
     */
    on_up = (e) => {
        this.POS.now = e.clientY;
        this.move_slide();
        this.add_on_mousedown_event();
    }//on_up
 
    /** 초기에 첫번째 슬라이드 빼고는 다 ㅋㅋ */
    set_move_slide(){
        const $$item = this.$slide.children;
        for(let i=this.maxLen - 1; i>=0; i--){
            const $item = $$item[i];
            const [$L,$C,$R] = $item.children;
            $L.style.transform = `translateY(100%)`;
            $C.style.transform = `translateY(-100%)`;
            $R.style.transform = `translateY(100%)`;
        }//for
    }//set_move_slide
 
    /** 슬라이드 움직이기 */
    move_slide(){
        if(!this.flagMove) return;
        this.flagMove = false;
        const {prev,now} = this.POS;
        const idxPrevSlide = this.idxCurrSlide;
        this.idxCurrSlide += prev - now > 0 ? 1 : -1;
        if(this.idxCurrSlide < 0){
            this.idxCurrSlide = this.maxLen;
        }else if(this.idxCurrSlide > this.maxLen){
            this.idxCurrSlide = 0;
        }
 
        const $itemPrev = this.$slide.children[this.maxLen - idxPrevSlide];
        const $itemCurr = this.$slide.children[this.maxLen - this.idxCurrSlide];
        const [$pL, $pC, $pR] = $itemPrev.children;
        const [$cL, $cC, $cR] = $itemCurr.children;
 
        $cL.style.transform = `translateY(0)`;
        $cC.style.transform = `translateY(0)`;
        $cR.style.transform = `translateY(0)`;
 
        if(idxPrevSlide < this.idxCurrSlide){            
            $cL.addEventListener('transitionend',()=>{
                $pL.style.transform = `translateY(100%)`;
                $pC.style.transform = `translateY(-100%)`;
                $pR.style.transform = `translateY(100%)`;
                this.flagMove = true;
            },{once:true})
        }else{        
            $cL.addEventListener('transitionend',()=>{
                $pL.style.transform = `translateY(-100%)`;
                $pC.style.transform = `translateY(100%)`;
                $pR.style.transform = `translateY(-100%)`;
                this.flagMove = true;
            },{once:true})
        }
    }//move_slide
 
    /** 버튼으로 슬라이드 움직이기 */
    add_btn_event(){
        this.$btns.addEventListener('click',(e)=>{
            if(e.target.tagName != "BUTTON"return;
            if(e.target.classList.contains('slide-btn-prev')){
                this.POS.prev = 0;
                this.POS.now = 1;
            }else{
                this.POS.prev = 1;
                this.POS.now = 0;
            }
            this.move_slide();
        });
    }//add_btn_event
}//class-YetAnoterSlide
 
const images = [
    "https://images.unsplash.com/photo-1549880338-65ddcdfd017b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2550&q=80",
    "https://images.unsplash.com/photo-1544198365-f5d60b6d8190?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2550&q=80",
    "https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80",
    'https://images.unsplash.com/photo-1673905110319-20284ef6f87d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80',
    'https://images.unsplash.com/photo-1673913817353-52d3bb7392a3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80'
];
new SliderBuilder()
.set_images(images)
.set_title("Hello World")
.init();
cs