CSS&JS/👀Study from Copying

[CSS,JS] requestAnimationFrame 으로 marquee 구성하기

arancia_ 2026. 4. 10. 14:01

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

대체 몇번쨰 marquee인지.. 이번엔 requestAnimationFrame으로 구현해보았습니다.
참고 사이트는 나의 경기도 웹진 

requestAnimationFrame으로 했을시 장점 및 유의할점
- 반응형 대응이 쉽다
- 마퀴 너비1/2이 분기점이라 flex를 먹이되 gap을 주는것보단 개체 오른쪽으로 padding을 주는게 낫다
- 마퀴의 width는 max-content가 되도록
- 속도 조절이 가능(CURR-- 부분)하긴 하나 경우에 따라 이상하게 튈수도 있다..! 특히 주사율이 높은 디스플레이에선 엄청나게 빨리 뛸 수 있음.
- 그래서 codepen에 올린것과 달리 하단에선 보간한걸로 ...

 

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>marquee</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js" type="module"></script>
</head>
 
<body>
    <section class="mq-wrap">
        <div class="mq"></div>
    </section>
</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
{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
 
html,
body {
    min-height: 100vh;
}
 
body {
    display: flex;
    flex-flow: column nowrap;
    justify-content: center;
    align-items: center;
}
 
.mq-wrap {
    position: relative;
    overflow: hidden;
    width: 100%;
    max-width: 1200px;
    border: 1px solid black;
}
 
.mq {
    display: flex;
    flex-flow: row nowrap;
    justify-content: flex-start;
    align-items: flex-start;
    position: relative;
    width: max-content;
    will-change: transform;
    /* transition: transform 10s linear; */
}
 
.mq.noTransition {
    transition: none;
}
 
.mq-link {
    flex: none;
    display: inline-block;
    position: relative;
    padding-right: 1.5em;
    font-size: clamp(18px, 4.2vw, 40px);
    font-weight: bold;
    color: #000;
    white-space: nowrap;
    text-decoration: none;
}
 
.mq-link:hover {
    color: red;
}
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
const $mq = document.querySelector(".mq");
let CURR = 0;
let MIN = null;
let MOVING = false;
const SPEED = 100//초당 100px 이동
let lastTime = 0;
 
init_marquee([
    "00 Hello World",
    "01 requestAnimationFrame",
    "02 marquee",
    "03 반응형 대응도 자동으로 되겠군요",
]);
 
$mq.addEventListener("mouseenter", () => {
    MOVING = false;
});
$mq.addEventListener("mouseleave", () => {
    MOVING = true;
    requestAnimationFrame(move_marquee2);
});
 
/* ======================================= */
/**
 * 
 * @param {*} arr 
 */
function init_marquee(arr = []) {
    make_marquee({ arr, isHidden: false });
    make_marquee({ arr, isHidden: true });
 
    MOVING = true;
    requestAnimationFrame(move_marquee2);
 
    //옛날방식
    // requestAnimationFrame(() => {
    //     move_marquee();
    // });
}//init_marquee
 
/**
 * 
 * @param {*} param0 
 */
function make_marquee({ arr = [], isHidden = false }) {
    arr.forEach(item => {
        const $a = document.createElement("A");
        $a.classList.add(`mq-link`);
        $a.target = "_blank";
        $a.href = "#";
        $a.textContent = `#${item}`;
        if (isHidden) {
            $a.classList.add(`cloned`);
            $a.setAttribute("aria-hidden""true");
            $a.setAttribute("tabindex"-1);
        }
        $mq.appendChild($a);
    });
}//make_marquee
 
/**
 * 
 * @returns 
 */
function move_marquee2(timestamp) {
    if (!MOVING) {
        lastTime = 0;
        return;
    }
    if (!lastTime) {
        lastTime = timestamp;
    }
    /* 프레임 간 시간 간격 계산(초단위) */
    const deltaTime = (timestamp - lastTime) / 1000;
    lastTime = timestamp;
 
    /* 최소값 정해주고 */
    if (!MIN || MIN !== ($mq.offsetWidth / 2* -1) {
        MIN = ($mq.offsetWidth / 2* -1;
    }
 
    /* 시간 간격에 따라 이동거리 계산 */
    CURR -= SPEED * deltaTime;
 
    /* 최소값보다 적으면 처음으로 돌아가고 */
    if (CURR < MIN) { CURR = 0; }
 
    /* 움직이고 */
    $mq.style.transform = `translate3d(${CURR}px,0,0)`;
 
    /* 반복 */
    requestAnimationFrame(move_marquee2);
}//move_marquee2
 
/**
 * 옛날방식...실시간 윈도우창 너비 변경시 대응 안됨
 * @deprecated
 */
function move_marquee() {
    const wid = $mq.offsetWidth;
    const ani = $mq.animate([
        {
            transform: `translate3d(0,0,0)`
        },
        {
            transform: `translate3d(-${wid / 2}px,0,0)`
        }
    ], {
        duration: 10000,
        fill: "forwards",
        easing: "linear"
    });
    ani.addEventListener("finish", () => {
        const ani2 = $mq.animate([
            {
                transform: `translate3d(0,0,0)`
            }
        ], {
            duration: 1,
            fill: "forwards",
            easing: "steps(1,end)"
        });
        ani2.addEventListener("finish", () => {
            move_marquee();
        });
    }, { once: true });
}
cs