CSS&JS/👀Study and Copy

[Hyperplexed] 카운터 애니메이션

arancia_ 2023. 12. 12. 09:44

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

데모 사이트 : https://ohikmyeong.github.io/hpp-counter

 

If Dr. Seuss Made Programming Tutorials

 

ohikmyeong.github.io

 

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
35
36
37
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>If Dr. Seuss Made Programming Tutorials</title>
    <link rel="stylesheet" href="./css/main.css">
    <script src="./js/main.js" type="module"></script>
</head>
<body>
    <a href="https://www.youtube.com/watch?v=HIrDMR6CaHY" target="_blank">If Dr. Seuss Made Programming Tutorials</a>
    
 
    <section class="counter-wrap">
        <p class="counter-title">CASH PRIZE</p>
        <div id="counter">
            <div class="counter-dollar">$</div>
            <div class="counter-number-wrap"></div>
            <div class="counter-rest"></div>
            <div class="counter-number-wrap"></div>
            <div class="counter-number-wrap"></div>
            <div class="counter-number-wrap"></div>
            <div class="counter-rest"></div>
            <div class="counter-number-wrap"></div>
            <div class="counter-number-wrap"></div>
            <div class="counter-number-wrap"></div>
        </div><!-- counter -->
        <div class="counter-bg"></div>
    </section><!-- wrap -->
 
    <form id="frm">
        <input type="color" name="counter-color" value="#4fe388">
        <input type="number" name="cash-prize" min="0" max="9999999">
        <button type="submit" id="btn-restart" title="restart">restart</button>
    </form>
</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
@charset "utf-8";
/* wrap */
.counter-wrap{
    --_bg:#343434;
    --counter-wrap-color :rgb(79,227,136);
    display:flex;flex-flow:column nowrap;
    justify-content:center; align-items:center;
    position:relative;
    width:min(1200px, 90%); min-width:1000px;
    padding:80px 20px 100px;
    background:var(--_bg);
    border:12px solid #555;
    border-radius:4px;
    text-align:center; font-weight:bold;color:var(--counter-wrap-color);
    user-select:none;
}
 
/* title */
.counter-title{
    position:relative;
    font-size:30px;
}
.counter-title::before,
.counter-title::after{
    content : "*";
}
.counter-title::before{margin-right:1em;}
.counter-title::after{margin-left:1em;}
 
/* counter */
#counter{
    display:flex;flex-flow:row nowrap;
    justify-content:center; align-items:flex-end;
    gap:10px;
    position:relative;
    width:100%;
    font-size:100px;
}
 
/* dollar */
.counter-dollar{
    margin-right:20px;
    font-weight:normal;
    font-size:.8em;
}
 
/* rest */
.counter-rest{
    width:12px; height:18px;
    background:var(--counter-wrap-color);
}
 
/* number */
.counter-number-wrap{
    position:relative; overflow:hidden;
    width:1ch; height:1.35ch;
    border-bottom:1px solid var(--counter-wrap-color);
    text-align:center; font-weight:bold;
}
 
/* number-list */
.counter-number-list{
    position:absolute;
    top:0; left:0;
    width:100%;
}
/* number-list-item */
.counter-number-list-item{
    width:100%; height:1.35ch;
}
 
/* background */
.counter-bg{
    position:absolute; overflow:hidden;
    top:0;left:0;
    width:100%; height:100%;
    pointer-events:none;
    background:repeating-linear-gradient(var(--_bg), var(--_bg) 3px, transparent 3px, transparent 9px);
    background-size:100% 200%;
    animation: move-background 60s linear 0s infinite;
}
.counter-bg::after{
    content:'';display:block;position:absolute;
    top:0;left:0;
    width:100%;height:100%;
    background:radial-gradient(transparent, rgba(0,0,0,.5), rgba(0,0,0,1));
    animation: breath-background 1s ease-in-out 0s infinite alternate;
}
@keyframes move-background {
    to{background-position-y:100%;}
}
@keyframes breath-background {
    to{ scale:1.5; }
}
 
cs

main.js

1
2
3
4
5
6
7
8
9
import { Counter } from "./Counter.js";
 
const cashPrize = 9_876_543;
 
document.getElementsByName('cash-prize')[0].value = cashPrize;
 
new Counter()
.set_cash_prize(cashPrize)
.init();
cs

Counter.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
export class Counter {
    #cashPrize;
 
    constructor() {
        this.$counterWrap = null;
        this.$$numberWrap = null;
        this.$form = null;
        this.clssList = {
            numberWrap: "counter-number-wrap",
            numberList: "counter-number-list",
            numberItem: "counter-number-list-item",
            target: 'number-target',
        }
    }//constructor
 
    set_cash_prize(cashPrize) {
        this.#cashPrize = String(cashPrize) ?? 0;
        console.log("this.#cashPrize",this.#cashPrize, typeof this.#cashPrize);
        return this;
    }//set_cash_prize
 
    init() {
        /* DOM */
        this.$counterWrap = document.getElementsByClassName('counter-wrap')[0];
        this.$$numberWrap = Array.from(this.$counterWrap.getElementsByClassName(this.clssList.numberWrap));
        this.$form = document.getElementById('frm');
 
        /* DOM + animation */
        this.restart();
 
        /* (EVENT) */
        this.$form.addEventListener('submit'this.on_submit_form);
    }//init
 
    restart() {
        /* DOM */
        this.fill_number_wrap();
 
        /* (FUNC) */
        this.animate_number_wrap();
    }//restart
 
    fill_number_wrap() {
        const lenWrap = this.$$numberWrap.length;
        const lenNum = this.#cashPrize.length;
        let startIdx = lenWrap - lenNum;
        let targetNumIdx = 0;
        if (startIdx < 0) { return console.error('액수가 너무 큼') }
 
        this.$$numberWrap.forEach(($numberWrap, idx) => {
            /* reset */
            $numberWrap.innerHTML = "";
 
            /* list */
            const $list = document.createElement('UL');
            $list.classList.add(this.clssList.numberList);
            $numberWrap.appendChild($list);
 
            /* 반복횟수 */
            const iterCount = idx + 1;
 
            /* fill list */
            for (let i = 0; i < iterCount; i++) {
                for (let num = 0; num <= 9; num++) {
                    const $item = document.createElement('LI');
                    $item.classList.add(this.clssList.numberItem);
                    $item.textContent = num;
                    $list.appendChild($item);
 
                    if (i !== iterCount - 1continue;
                    if (startIdx !== idx) continue;
                    const answer = this.#cashPrize.at(targetNumIdx);
                    if (answer !== String(num)) continue;
                    $item.classList.add(this.clssList.target);
                    startIdx++;
                    targetNumIdx++;
                }//for-num
            }//for-iterCount
        });//forEach
    };
 
    /**
     * @url https://easings.net/ko#easeOutBack
     */
    animate_number_wrap() {
        const len = this.$$numberWrap.length;
        
        for (let i = 0; i < len; i++) {
            const $numberWrap = this.$$numberWrap[i];
            const $list = $numberWrap.getElementsByClassName(this.clssList.numberList)[0];
            const $target = $numberWrap.getElementsByClassName(this.clssList.target)[0];
            if(!$target){continue;}
            const top = $target.offsetTop * -1;
            $list.animate([
                { transform: `translateY(${top}px)` }
            ], {
                duration: 4000,
                fill: "both",
                easing: "cubic-bezier(0.34, 1.25, 0.7, 1)",
                delay : (len - i) * 100
            });
        }//for
    }//animate_number_wrap
 
    /**
     * form 제출시
     * @param {Event} e 
     */
    on_submit_form = e => {
        e.preventDefault();
        const valueColor = e.currentTarget.querySelector("[name='counter-color']").value;
        const cashPrize =  e.currentTarget.querySelector("[name='cash-prize']").value;
        
        this.$counterWrap.style.setProperty('--counter-wrap-color', valueColor);
        if(String(cashPrize) === this.#cashPrize) return;
        this.set_cash_prize(cashPrize);
        this.restart();
    }//on_submit_form
}//Counter
cs