CSS&JS/👀Study and Copy

[LunDev]CSS only marquee slider

arancia_ 2024. 9. 19. 12:02

css로만 만들 수 있는 marquee slider. 마우스 hover시 잠시 애니메이션이 멈춘다(animation-play-state:paused)

https://www.youtube.com/watch?v=mF9yOwlunWk

다만 매번 아이템 추가하고 지우고 변경하고 할 때 총 갯수 지정 및 이런게 귀찮으니 JS로 생성 가능하게 만들어봄

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

 

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Play and Pause in Infinite Slider with CSS Only</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js" type="module"></script>
</head>
<body>
    <a href="https://www.youtube.com/watch?v=mF9yOwlunWk">Play and Pause in Infinite Slider with CSS Only</a>
</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
@charset "utf-8";
 
*{margin:0;padding:0;box-sizing:border-box;}
li{list-style-type:none;}
a{
    display:block;
    padding:20px 10px;
    background:#000;
    text-align:center;color:yellow;
}
 
body{
    background:#dedede;
}
 
.slider-wrap{
    position:relative; overflow:hidden;
    width:100%; max-width:1200px;
    margin:20px auto;
    border:2px solid black;
    border-radius:10px;
}
 
.slider{
    --_time:15s;
    --_gap:20px;
    --_wid-full:calc(var(--_wid-item) * var(--_count) - var(--_wid-item));
    --_wid-gap:calc(var(--_gap) * (var(--_count) - 1));
    display:flex; flex-flow:row nowrap;
    justify-content:flex-start; align-items:center;
    gap:var(--_gap);
    position:relative;
    width:100%;
    height:var(--_hei-item);
    background:#fff;
}
 
.slider-item{
    --_delay-basic:calc((var(--_time) / var(--_count)) * var(--_i));
    flex:none;
    display:flex;flex-flow:row wrap;
    justify-content:center; align-items:center;
    position:relative;
    width:var(--_wid-item);
    height:var(--_hei-item);
    background:rgba(calc(7 * var(--_i)),calc(15 * var(--_i)),calc(5 * var(--_i)),.8);
    border:1px dashed rgb(32, 13, 13);
    font-size:30px;font-weight:bold;
    text-shadow:0px 0px 5px #fff, 0px 0px 4px #fff, 0px 0px 3px #fff, 0px 0px 2px #fff, 0px 0px 1px #fff, 0px 0px 5px #fff;
    transition:all .2s;
}
.slider.on .slider-item{
    position:absolute;
    left:100%;
    animation: slider-move var(--_time) linear infinite both;
    animation-delay:calc(var(--_delay-basic) - (var(--_time) - 1s));
}
.slider-move-reverse .slider-item{
    animation-direction: reverse;
}
 
@keyframes slider-move {
    from{left:100%;}
    to{left:calc(-1 * var(--_wid-item));}
}
 
.slider:hover .slider-item{
    animation-play-state:paused;
    filter:saturate(0) brightness(0.3);
    opacity:.5;
}
.slider:hover .slider-item:hover{
    filter:saturate(1)  brightness(1);
    opacity:1;
}
cs

main.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
import { MarqueeSliderBuilder } from "./MarqueeSlider.js";
 
const $slider1 = new MarqueeSliderBuilder()
.set_count(10)
.set_item_width("150px")
.set_item_height("50px")
.set_item_gap("20px")
.is_move_reverse(false)
.set_time("10s")
.init();
 
const $slider2 = new MarqueeSliderBuilder()
.set_count(5)
.set_item_width("300px")
.set_item_height("350px")
.set_item_gap("5px")
.is_move_reverse(true)
.set_time("10s")
.init();
 
const $slider3 = new MarqueeSliderBuilder()
.set_count(20)
.set_item_width("100px")
.set_item_height("100px")
.set_item_gap("30px")
.is_move_reverse(true)
.set_time("10s")
.init();
 
document.body.appendChild($slider1);
document.body.appendChild($slider2);
document.body.appendChild($slider3);
cs

 

MarqueeSlider.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
import { MarqueeSliderView } from "./MarqueeSliderView.js";
 
/**
 * 마퀴 슬라이더
 */
class MarqueeSlider {
    constructor(setting = {}) {
        this.itemCount = setting?.itemCount;
        this.itemWidth = setting?.itemWidth;
        this.itemHeight = setting?.itemHeight;
        this.isReverse = setting?.isReverse;
        this.itemGap = setting?.itemGap ?? "0px";
        this.time = setting?.time ?? "10s";
    }//constructor
 
    /**
     * 마퀴슬라이더 만들어 배포
     */
    init(){
        /* wrap */
        const $wrap = MarqueeSliderView.make_wrap();
        
        /* slider */
        const $slider = MarqueeSliderView.make_slider({
            itemCount : this.itemCount,
            itemWidth : this.itemWidth,
            itemHeight : this.itemHeight,
            itemGap : this.itemGap,
            isReverse : this.isReverse,
            time : this.time,
        });
        $wrap.appendChild($slider);
        
        /* fill items */
        const $$item = MarqueeSliderView.make_items(this.itemCount);
        $slider.appendChild($$item);
 
        /* 움직이기 시작 */
        $slider.classList.add("on");
 
        /* 최종 */
        return $wrap;
    }//init
}//class-MarqueeSlider
 
/**
 * 마퀴 슬라이더 생성기
 */
export class MarqueeSliderBuilder {
    constructor() {
        this.setting = {
            itemCount: 0,
            itemWidth: null,
            itemHeight: null,
            isReverse: false,
            time: "10s",
            itemGap: "0px",
        };
    }
 
    set_count(itemCount = 0) {
        this.setting.itemCount = itemCount;
        return this;
    }//set_count
 
    set_item_width(itemWidth = 0) {
        this.setting.itemWidth = itemWidth;
        return this;
    }//set_item_width
 
    set_item_height(itemHeight = 0) {
        this.setting.itemHeight = itemHeight;
        return this;
    }//set_item_height
 
    is_move_reverse(moveReverse = true) {
        this.setting.isReverse = moveReverse;
        return this;
    }//is_move_reverse
 
    set_time(time = "10s") {
        this.setting.time = time;
        return this;
    }//set_time
 
    set_item_gap(itemGap = "0px") {
        this.setting.itemGap = itemGap;
        return this;
    }
 
    init() {
        return new MarqueeSlider(this.setting).init();
    }//init
}//class-MarqueeSliderBuilder
cs

 

MarqueeSliderView.js

DOM 생성관련만 모아둔 클래스

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
export class MarqueeSliderView {
    /**
     * 
     * @returns 
     */
    static make_wrap() {
        const $wrap = document.createElement("DIV");
        $wrap.classList.add("slider-wrap");
        return $wrap;
    }//make_wrap
 
    /**
     * 
     * @returns 
     */
    static make_slider(INFO = {}) {
        const { itemCount, itemWidth, itemHeight, itemGap, isReverse, time } = INFO;
 
        const $slider = document.createElement("UL");
        $slider.classList.add("slider");
        $slider.style.setProperty("--_time", time);
        $slider.style.setProperty("--_gap", itemGap);
        $slider.style.setProperty("--_count", itemCount);
        $slider.style.setProperty("--_wid-item", itemWidth);
        $slider.style.setProperty("--_hei-item", itemHeight);
 
        const widFull = (itemCount - 1* parseInt(itemWidth);
        const widGap = (itemCount - 1* parseInt(itemGap);
        $slider.style.width = `${widFull + widGap}px`
 
        if(isReverse){$slider.classList.add("slider-move-reverse");}
 
        return $slider;
    }//make_slider
 
    /**
     * 
     * @param {Number} itemCount 
     */
    static make_items(itemCount) {
        const $frag = document.createDocumentFragment();
 
        for (let i = 0; i < itemCount; i++) {
            const $item = document.createElement("LI");
            $item.classList.add("slider-item");
            $item.textContent = i + 1;
            $item.style.setProperty("--_i", i);
            $frag.appendChild($item);
        }//for
 
        return $frag;
    }//make_items
}//class-MarqueeSliderView
cs