CSS&JS/⚡Thinkers

[JS]바닐라 자바스크립트로 swiper 흉내내보기 v1.0

arancia_ 2022. 2. 18. 17:40

1.0인 이유는 아직 터치를 구현 안해서...

 

깃허브 https://github.com/OhIkmyeong/custom_slider

 

GitHub - OhIkmyeong/custom_slider: 짭 swiper(기능 한참 못미침 ㅎㅎ ㅠ)

짭 swiper(기능 한참 못미침 ㅎㅎ ㅠ). Contribute to OhIkmyeong/custom_slider development by creating an account on GitHub.

github.com

 

디즈니플러스나 농협라이블리 같은 메인페이지의 슬라이더들 보면

  • 무한으로 좌우 스크롤링 됨
  • 현재 화면에 포커스 된 애들은 scale이 1.0 나머지는 그보단 작음
  • (n-1) n (n+1) 3개의 엘리먼트가 한 화면에 보임.

현재까지 구현한거

  • 이전/다음버튼으로 무한스크롤링
  • 드래그 까진 아니지만 마우스로 무한스크롤링(mousedown, mouseend)
  • 자동재생 on/off

미흡한거

  • 한계점에선 뚝뚝 끊겨서 부드러운 무한 스크롤링은 아님 ㅋㅋ ㅠ 에라이~~~

구현해야 할거

  • 터치
  • 자동재생 남은 시간 표시

어쨌든 에센셜은 있다구용?!

 

 

index.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
<!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>copying swiper</title>
<link rel="stylesheet" type="text/css" href="./slider/slider.css"/>
<script src="./main.js" type="module"></script>
</head>
<body>
    <h1>짭 swiper</h1>
 
    <ul class="swiper">
        <li><div class="swiper-content">0</div></li>
        <li><div class="swiper-content">1</div></li>
        <li><div class="swiper-content">2</div></li>
        <li><div class="swiper-content">3</div></li>
    </ul>
 
    <footer><a href="https://aosceno.tistory.com/" target="_blank">티스토리</a></footer>
 
    <template>
        <!-- swiper-wrap을 만들고 그 안에 swiper를 추가한다. -->
        <!-- swiper-wrap 안에 추가해줍니다 -->
        <button class="swiper-btn" data-btn="prev">이전</button>
        <button class="swiper-btn" data-btn="next">다음</button>
    </template>
</body>
</html>
cs



slider.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
@charset "utf-8";
*{margin:0;padding:0;box-sizing:border-box;}
li{list-style-type:none;}
 
html,body{overflow-x:hidden;}
 
.swiper-wrap{
    outline:1px solid red;
    position:relative; overflow:hidden;
    width:100%; aspect-ratio:5/1.5;
    margin:2rem auto;
    background:#eee;}
 
 
.swiper-wrap .swiper{
    display:flex; flex-flow:row nowrap;
    justify-content:flex-start; align-items:center;
    position:relative;
    height:100%;
    transition:transform .5s;
    cursor:grab;
}
 
.swiper-wrap .swiper li{
    flex:none;
    position:relative;
    width:calc(100% - 400px); height:100%;
    border:2px solid red;}
 
.swiper-wrap .swiper li .swiper-content{
    display:flex;
    justify-content:center;align-items:center;
    position:relative;
    width:100%;height:100%;
    background:#333;
    text-align:center; font-size:10vw; color:#fff;
    transform:scale(.8);
    transition:transform .5s;
    pointer-events:none; user-select:none;
}
 
    .swiper-wrap .swiper li.on > div{transform:scale(1);}
 
 
/* 버튼 */
.swiper-wrap .swiper-btn{
    position:absolute;
    top:100%; left:50%; 
    padding:1rem;
    cursor:pointer;}
 
.swiper-wrap .swiper-btn[data-btn="prev"]{
    transform:translate(-50%,-100%);
}
.swiper-wrap .swiper-btn[data-btn="next"]{
    transform:translate(50%,-100%);
}
 
 
/* 자동재생 버튼 */
.btn_timer{
    position:absolute;
    right:0; bottom:0;
    padding:1rem;
}
 
 
cs



main.js

1
2
3
4
5
import { Swiper } from "./slider/slider.js";
 
const $swiper = document.getElementsByClassName('swiper')[0];
 
new Swiper($swiper);
cs



slider.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
import { MakeSlider } from "./makeSlider.js";
import { PosSlider } from "./positionSlider.js";
import { Timer } from "./timeSlider.js";
 
export class Swiper{
    constructor($elem){
        this.MAKE = new MakeSlider(this);
        this.POS = new PosSlider(this);
        this.TIMER = new Timer(this);
 
        this.$wrap = undefined;
        this.$elem = $elem;
 
        this.flag = true;
 
        this.init();
    }//constructor
 
    init(){
        this.MAKE.init();
        this.POS.init();
        this.add_event();
        this.TIMER.init();
    }//init
 
    add_event(){
        this.$wrap.addEventListener('click',this.on_btn);
        this.$wrap.addEventListener('mousedown',this.POS.mousePos_start);
        this.$wrap.addEventListener('mouseup',this.POS.mousePos_end);
    }//add_event
 
    on_btn = (e) => {
        if(!this.flag){return;}
        const direction = e.target.dataset.btn; 
        if(!direction){return;}
 
        this.flag = false;
        this.TIMER.clear_timer();
 
        switch(direction){
            case "prev" :
                this.POS.curr--;
                break;
 
            case "next" : 
                this.POS.curr++;
                break;
        }//switch
 
        this.POS.move_to();
    }//on_btn;
 
}//class-Swiper
cs



makeSlider.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
export class MakeSlider{
    constructor(SLIDER){
        this.SLIDER = SLIDER;
    }
 
    /* 총괄 */
    init(){
        this.put_in_to_wrap();
        this.add_li();
        this.make_btn();
    }//init
 
    /* wrap 만들어서 그 안으로 옮긴다 */
    put_in_to_wrap(){
        const $elem = this.SLIDER.$elem;
        const $parent = $elem.parentElement || document.body;
        const $wrap = document.createElement('DIV');
        $wrap.classList.add('swiper-wrap');
        $parent.insertBefore($wrap,$elem);
        
        $wrap.appendChild($elem);
 
        this.SLIDER.$wrap = $wrap;
    }//put_in_to_wrap
 
    /* 버튼 추가 */
    make_btn(){
        const $wrap = this.SLIDER.$wrap;
        const $prev = document.createElement('BUTTON');
        const $next = document.createElement('BUTTON');
 
        $prev.classList.add('swiper-btn');
        $next.classList.add('swiper-btn');
 
        $prev.dataset.btn = "prev";
        $next.dataset.btn = "next";
 
        $prev.textContent = '이전';
        $next.textContent = '다음';
 
        $wrap.appendChild($prev);
        $wrap.appendChild($next);
    }//make_btn
 
    /* 0,1과 n과 n-1을 앞 뒤로 추가해줘야함 */
    add_li(){
        const $elem = this.SLIDER.$elem;
        const $$li = $elem.children;
        const $first = $$li[0]; 
 
        const $0 = $first.cloneNode(true);
        const $1 = $$li[1].cloneNode(true);
        const $n_1 = $$li[$$li.length - 2].cloneNode(true);
        const $n = $$li[$$li.length - 1].cloneNode(true);
 
        const $frag_next = document.createDocumentFragment();
        $frag_next.appendChild($0);
        $frag_next.appendChild($1);
 
        const $frag_before = document.createDocumentFragment();
        $frag_before.appendChild($n_1);
        $frag_before.appendChild($n);
 
        $elem.insertBefore($frag_before, $elem.firstChild);
        $elem.appendChild($frag_next);
    }//add_li
    
}//class-MakeSlider
cs



positionSlider.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
export class PosSlider{
    constructor(SLIDER){
        this.SLIDER = SLIDER;
 
        this.WID = {
            win : window.innerWidth,
            li : undefined};
        this.GAP = undefined;
        this.limit = {first : undefined, last:undefined};
        
        this.mousePos = {start:undefined, end:undefined};
 
        this.curr = 2;
        this.transition = `transform .5s`;
 
        this.time = 2000;
        this.timer = undefined;
    }//constructor
 
    init(){
        this.add_resize_event();
        this.set_size();
        this.set_limit();
 
        this.move_to(this.curr);
 
        this.SLIDER.TIMER.run_timer();
    }//init
 
    add_resize_event(){
        window.addEventListener('resize',()=>{
            this.set_size();
            this.set_limit();
            this.move_to(this.curr);
        });
    }//add_resize_event
 
    set_size(){
        this.WID.win = parseInt(window.innerWidth);
        this.WID.li = this.WID.win - 400;
        this.GAP = (this.WID.win - this.WID.li) / 2;
    }//set_size
 
    set_limit(){
        const len = this.SLIDER.$elem.children.length - 2;
        this.limit.first = (this.WID.li * -1+ this.GAP;
        this.limit.last = (this.WID.li * -1 * len) + this.GAP;
    }//set_limit
 
    async move_to(){
        const $elem = this.SLIDER.$elem;
        const movement = (this.WID.li * -1 * this.curr) + this.GAP;
        await this.move(movement);
 
        if(this.curr < 2){
            console.log('끝으로 변경해야돼');
            this.curr = $elem.children.length - 3;
            $elem.children[this.curr].classList.add('on');
            await this.remove_transition_and_move();
        }else if(this.curr >= $elem.children.length - 2){
            console.log('시작으로 변경해야돼');
            this.curr = 2;
            $elem.children[this.curr].classList.add('on');
            await this.remove_transition_and_move();
        }else{
            this.add_on();
        }//if
 
        this.SLIDER.flag = true;
    }//move_to
 
    move(movement){
        const $elem = this.SLIDER.$elem;
        $elem.style.transform = `translateX(${movement}px)`;
 
        return new Promise((res)=>{
            setTimeout(()=>{
                res('');
            },500);
        });
    }//move
 
    add_on(){
        const $$li = this.SLIDER.$elem.children;
        const curr = $$li[this.curr];
        curr.classList.add('on');
 
        for(let $li of $$li){
            if($li == curr){continue;}
            $li.classList.remove('on');
        }
        
    }//add_on
 
    async remove_transition_and_move(){
        await this.remove_transition();
        this.restore_transition();
    }//remove_transition_and_move
 
    
 
    remove_transition(){
        this.SLIDER.$elem.style.transition = 'none';
        this.move_to(this.curr);
 
        return new Promise((res)=>{
            setTimeout(()=>{
                res();
            },100);
        });
    }//remove_transition
 
    restore_transition(){
        this.SLIDER.$elem.style.transition = this.transition;
    }//restore_transition
 
 
    mousePos_start = (e)=>{
        this.mousePos.start = e.clientX;
        this.SLIDER.$elem.style.cursor = 'grabbing';
    }//mousePos_start
 
    mousePos_end = (e)=>{
        this.flag = false;
        
        this.SLIDER.$elem.style.cursor = 'grab';
        this.SLIDER.TIMER.clear_timer();
 
        this.mousePos.end = e.clientX;
        const {start, end} = this.mousePos;
        const gap = end - start;
        if(gap > 0){
            this.curr--;
        }else if(gap < 0){
            this.curr++;
        }
        this.move_to();
    }//mousePos_end
}//class-PosSlider
cs



timeSlider.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
export class Timer{
    constructor(SLIDER){
        this.SLIDER = SLIDER;
 
        this.time = 2000;
        this.timer = undefined;
        this.$btn = undefined;
    }//constructor
 
    init(){
        this.add_timer();
    }//init
 
    /* 자동재생 버튼 달기 */
    add_timer(){
        const $wrap = this.SLIDER.$wrap;
        const $btn_timer = document.createElement('BUTTON');
        $btn_timer.classList.add('btn_timer');
        $btn_timer.textContent = '자동 재생 off';
        $btn_timer.dataset.auto = "on";
        $wrap.appendChild($btn_timer);
 
        this.$btn = $btn_timer;
    }//add_timer
 
    run_timer(){
        this.SLIDER.POS.curr++;
        this.SLIDER.POS.move_to();
        this.timer = setTimeout(()=>{this.run_timer();},this.time);
    }//run_timer
 
    clear_timer(){
        clearTimeout(this.timer);
        this.toggle_btn();
    }//clear_timer
 
    toggle_btn(){
        const auto = this.$btn.dataset.auto;
        this.$btn.textContent = `자동 재생 ${auto}`;
        const change = auto == "on" ? "off" : "on";
        this.$btn.dataset.auto = change;
 
        if(change == "on"){this.run_timer();}
    }//toggle_btn
}//class-Timer
cs