CSS&JS/👀Study and Copy

[CSS+JS]3분할 슬라이드(2)

arancia_ 2023. 1. 20. 10:42

  • 이전 버젼을 만들고 나니 원본이랑 움직임이 다르기에 수정해줌, + 이미지가 정중앙에 오도록 바꿔줌
  • 이미지가 정중앙에 오도록 하는 방법
    • 이미지는 width:100vmax; height:100vh; object-fit:cover;
    • left는 left:50vw;에 translate(-50%);
    • center는 left:50%; translate(-50%);
    • right는 left:calc(((100vw / 3) * -2) + 50vw);
  • 3분할로 나뉜 애들 transition을 transform에만 걸고 opacity는 걸지 않아놔야 생각한대로 움직인다.
    • center로 움직일때는 opacity 1로 해야 빈화면이 안 보이더라고...머리로는 안 걸어도 될 것 같은데, 플래그 걸어놨음에도 불구하고 마구 움직이다보면 자꾸 opacity 0으로 씹히는 애들이 있었어서 center로 이동시 opacity 1 되게 해놓았음. 
  • flag를 걸어놔서 오류 최대한 안 나도록 해놨음.
  • 1때처럼 DOM에는 z-index 안 걸고 거꾸로 하려고 했는데 짜다보니 내가 헷갈려서 -_-(수포자) 그냥 z-index값을 변경시켰음.
    • prev,curr,next도 헷갈려서 1때와 다르게 동작이 끝난 후에 curr값을 변경시키는것으로 하였음...
  • 터치 이벤트도 추가함 

HTML은 동일함 (아무것도 없다는 뜻)

 

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
109
110
111
112
@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 0s cubic-bezier(0.68, 0.03, 0.29, 1);
}
.slide-item-center{
    left:var(--wid);
    transition:transform 1s .2s cubic-bezier(0.68, 0.03, 0.29, 1);
}
.slide-item-right{
    right:0;
    transition:transform 1s 0s cubic-bezier(0.68, 0.03, 0.29, 1);
}
 
/* 3분할 속 이미지 */
[class ^="slide-item-"] img{
    position:absolute;
    top:0;left:0;
    width:100vmax; height:100vh; object-fit:cover;
}
.slide-item-left img{
    left:50vw;
    transform:translate(-50%);
}
.slide-item-center img{
    left:50%;
    transform:translateX(-50%);
}
.slide-item-right img{
    left:calc(((100vw / 3) * -2) + 50vw);
    transform:translateX(-50%);
}
 
/* 이전 다음 버튼 */
#slide-btns{
    position:absolute;
    z-index:200;
    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; z-index:100;
    top:50%;left:50%;
    transform:translate(-50%,-50%);
    text-align:center;
    font-size:10vmin; color:rgba(255,255,255,1);
    text-shadow:0 0 5px rgba(0,0,0,.5);
    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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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{
    /**
     * @param {Class} Builder 
     * @memo https://codepen.io/theseventh/pen/LYVqMYb by Arseny M
     */
    constructor(Builder){
        this.images = Builder.images;
        this.title = Builder?.title;
        this.$wrap = null;
        this.$slide = null;
        this.$$item = [];
        this.$btns = null;
 
        this.POS = {prev:null, now:null};
        this.idxCurr = 0;
        this.maxLen = Builder.images.length - 1;
 
        this.flagMove = true;
    }//constructor
 
    init(){
        //DOM 추가
        this.make_wrap();
        this.make_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_mousedown_event();
        this.add_btn_event();
 
        //슬라이드 미리 이동시켜놓기
        this.item_before_init();
    }//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=0; i<=this.maxLen; i++){
            const $li = document.createElement('LI');
            const url = this.images[i];
            $li.classList.add('slide-item');
            $li.style.zIndex = this.maxLen - i + 1;
            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);
            });
            this.$$item.push($li);
            $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_mousedown_event(){
        this.$slide.addEventListener('mousedown',this.on_down, {once:true});
        this.$slide.addEventListener('touchstart'this.on_down, {once:true});
    }
 
    /** 
     * mousedown시 이벤트 
     * 마우스 다운 당시의 포지션y값을 저장
     * 마우스를 뗐을 때의 이벤트를 추가한다.
     * */
    on_down = (e) =>{
        this.POS.prev = e.type == "touchstart" ? e.touches[0].clientY : e.clientY;
        this.$slide.addEventListener('mouseup',this.on_up, {once:true});
        this.$slide.addEventListener('touchend',this.on_up, {once:true});
    }//on_down
 
 
    /** 
     * mouseup시 이벤트
     * 마우스를 뗐을때의 포지션 y값을 저장
     * 슬라이드를 이동시키고
     * 다시 mousedown이벤트를 추가해준다.
     */
    on_up = (e) => {
        this.POS.now = e.type == "touchend" ? e.changedTouches[0].clientY : e.clientY;
        this.move_slide();
        this.add_mousedown_event();
    }//on_up
 
    /** 미리 위치시켜놓기 */
    item_before_init(){
        this.item_up(this.$$item[this.maxLen]);
        this.item_down(this.$$item[this.idxCurr + 1]);
        for(let i=2; i<this.maxLen; i++){
            this.item_down(this.$$item[i], true);
        }
    }//item_before_init
 
    /** 
     * 아이템 위로 이동(next 동작)
     * @param {DOM}$item
     * @param {Boolean}hide 
     * */
    item_up($item,hide=false){
        const [$L,$C,$R] = $item.children; 
        if(hide) $item.style.opacity = 0;
        $L.style.transform = `translateY(100%)`;
        $C.style.transform = `translateY(-100%)`;
        $R.style.transform = `translateY(100%)`;
 
        $C.addEventListener('transitionend',()=>{
            $item.style.opacity = 1;
            this.flagMove = true;
        },{once:true});
    }//item_up
 
    /** 아이템 현재 화면으로 이동(0) */
    item_center($item){
        const [$L,$C,$R] = $item.children; 
        $L.style.transform = `translateY(0)`;
        $C.style.transform = `translateY(0)`;
        $R.style.transform = `translateY(0)`;
        $item.style.opacity = 1;
    }//item_up
 
    /** 아이템 아래로 이동(prev 동작) 
     * @param {DOM}$item
     * @param {Boolean}hide 
    */
    item_down($item,hide){
        const [$L,$C,$R] = $item.children; 
        if(hide) $item.style.opacity = 0;
        $L.style.transform = `translateY(-100%)`;
        $C.style.transform = `translateY(100%)`;
        $R.style.transform = `translateY(-100%)`;
        $C.addEventListener('transitionend',()=>{
            $item.style.opacity = 1;
            this.flagMove = true;
        },{once:true});
    }//item_down
 
    /** 적합한 인덱스값 가져오기 */
    get_proper_idx(idx){
        return idx < 0 ? this.maxLen : (idx > this.maxLen ? 0 : idx);
    }//get_proper_idx
 
    /** 슬라이드 움직이기 */
    move_slide(){
        if(!this.flagMove) return;
        this.flagMove = false;
        const {prev,now} = this.POS;
        const idxPrev = this.get_proper_idx(this.idxCurr - 1);
        const idxNext = this.get_proper_idx(this.idxCurr + 1);
 
        const $itemCurr = this.$$item[this.idxCurr];
        const $itemPrev = this.$$item[idxPrev];
        const $itemNext = this.$$item[idxNext];
 
 
        if(prev > now){
            //다음
            this.item_up($itemCurr);
            this.item_center($itemNext);
            this.item_down(this.$$item[this.get_proper_idx(idxNext + 1)],true);
        }else{
            //이전
            this.item_down($itemCurr);
            this.item_center($itemPrev);
            this.item_up(this.$$item[this.get_proper_idx(idxPrev - 1)], true);
        }
 
        this.idxCurr += prev - now > 0 ? 1 : -1;
        this.idxCurr = this.get_proper_idx(this.idxCurr);
    }//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-1545231097-cbd796f1d95f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1395&q=80",
    'https://images.unsplash.com/photo-1673921541439-f5019af459d7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1469&q=80'
];
new SliderBuilder()
.set_images(images)
.set_title("Hello World")
.init();
cs