CSS&JS/⚡Thinkers

[CSS,JS] 24시간 쿠키, 드래그 가능한 레이어 팝업CSS,JS] 24시간 쿠키, 드래그 가능한 레이어 팝업

arancia_ 2026. 3. 20. 15:45

이전에 작성한 포스팅 기반으로 2개 합침.
예전에는 스크롤 가능한 환경에선 드래그 도중에 튀는 경우가 있어서
레이어 팝업의 position값을 fixed에서 abosilute; 로 변경하였음.

24시간 쿠키 : https://aosceno.tistory.com/646

 

[JS]바닐라 자바스크립트로 24시간 쿠키 모달 끄기/켜기 설정하기

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 24hours modal 쿠키 제거 모달 보기 Lorem ipsum, dolor sit amet consectetur adipisicing elit. Molestias, perspiciatis! 24시간동안 보지 않기 닫

aosceno.tistory.com

드래그 + 크기 조절 모달 : https://aosceno.tistory.com/689

 

[JS]Draggable & Resize 모달창

비슷한 내용을 자주 올리지만 다시 짤 때마다 원리를 이해한 상태에서 더 간결해지고 있으므로... 드래그나 리사이즈를 할 때마다 해당 모달의 z-index가 가장 상위로 올라온듯하게 보이도록 수정

aosceno.tistory.com

 

codepen에서는 24h 쿠키 확인이 안 되긴 함

See the Pen Layer Popup:24hour cookie, Draggable by Oh Ikmyeong (@dpffpself) on CodePen.

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
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>팝압</title>
    <link rel="stylesheet" href="./css/style.css">
    <link rel="stylesheet" href="./css/popup.css">
    <script src="./js/main.js" type="module"></script>
</head>
 
<body style="min-height:150vh;">
    <button id="delete_cookie">쿠키 삭제</button>
    <section class="popup off" data-popupid="popup001">
        <article class="popup-ctnt">
            <img src="https://www.gg.go.kr/uploads/CONTENTS/site/gg/wolym26_prmp_keyvisual_pc.webp" class="popup-img"
                draggable="false" />
            <a href="#" target="_blank" class="popup-link" draggable="false">바로가기</a>
        </article>
        <article class="popup-btm">
            <label class="popup-lbl">
                <input class="popup-chk" type="checkbox" name="popup24h" value="popup001">
                24시간동안 보지 않기
            </label>
        </article>
        <button class="popup-close">X</button>
    </section>
    <section class="popup off" data-popupid="popup002">
        <article class="popup-ctnt">
            <img src="https://www.gg.go.kr/uploads/CONTENTS/site/gg/wolym26_prmp_keyvisual_pc.webp" class="popup-img"
                draggable="false" />
            <a href="#" target="_blank" class="popup-link" draggable="false">바로가기</a>
        </article>
        <article class="popup-btm">
            <label class="popup-lbl">
                <input class="popup-chk" type="checkbox" name="popup24h" value="popup002">
                24시간동안 보지 않기
            </label>
        </article>
        <button class="popup-close">X</button>
    </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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
.popup {
    position: absolute;
    top: 0;
    left: 0;
    max-width: 500px;
    border: 1px solid black;
}
 
.popup.off {
    opacity: 0;
    pointer-events: none;
}
 
.popup[data-popupid="popup001"] {
    transform: translate(100px, 100px);
}
 
.popup[data-popupid="popup002"] {
    transform: translate(200px, 200px);
}
 
.popup-ctnt {
    position: relative;
    width: 100%;
}
 
.popup-img {
    width: 100%;
    height: auto;
    max-width: 500px;
    user-select: none;
    vertical-align: middle;
}
 
.popup-link {
    position: absolute;
    left: 50%;
    bottom: 20px;
    transform: translate(-50%, 0);
    padding: 1em 3em;
    background: #000;
    color: #fff;
}
 
.popup-btm {
    position: relative;
    padding: 10px;
    background: #000;
    font-size: 12px;
    color: #fff;
}
 
.popup-lbl {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 5px;
    position: relative;
 
}
 
.popup-close {
    position: absolute;
    top: 0;
    right: 0;
    width: 50px;
    aspect-ratio: 1/1;
    background: rgba(0, 0, 0, .2);
    border: 1px solid black;
    border: none;
    backdrop-filter: blur(10px);
    color: #fff;
    cursor: pointer;
}
cs

main.js

1
2
3
import { LayerPopup } from "./LayerPopup.js";
 
new LayerPopup().init();
cs

LayerPopup.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
import { Draggable } from "./Draggable.js";
 
/**
 * 레이어 팝업
 */
export class LayerPopup {
    constructor() {
        this.$btnDeleteCookie = document.getElementById("delete_cookie");
    }//constructor
 
    /**
     * 
    */
    init() {
        /* 팝업 전체 */
        const $$popup = document.querySelectorAll(".popup");
 
        /* 쿠키 삭제 버튼 클릭시 */
        this.$btnDeleteCookie.addEventListener("click", () => {
            console.log("쿠키 삭제 렛츠고");
            $$popup.forEach($popup => {
                const popupid = $popup.dataset.popupid;
                if (!popupid) { throw new Error("no popupid"); }
                this.delete_cookie(popupid);
            });
        });
 
        /* 닫기 기능 추가 */
        $$popup.forEach($popup => {
            const popupid = $popup.dataset.popupid;
            if (!popupid) { throw new Error("no popupid"); }
            const isClose = this.get_cookie(popupid);
 
            /* 닫힌게 아니라면 */
            if (!isClose) {
                /* 팝업 보이게 */
                $popup.classList.remove("off");
                /* 닫기 버튼 클릭 이벤트 추가 */
                const $close = $popup.querySelector(".popup-close");
                $close.addEventListener("click", () => {
                    this.on_close($popup);
                }, { once: true });
 
                /* 드래그 이벤트 추가 */
                new Draggable()
                    .set_dom($popup)
                    .init();
            }
 
 
        });
    }//init
 
    /**
     * 
     * @param {*} $popup 
     */
    on_close = ($popup) => {
        /* 24h 체크 */
        const $check = $popup.querySelector(".popup-chk");
        if ($check.checked) {
            this.set_cookie({
                popupid: $check.value,
                value: "done",
                expiredays: 1
            });
        }
 
        /* 닫기 */
        const $parent = $popup.parentElement;
        $parent.removeChild($popup);
    }//on_close
 
    /**
     * 
     * @param {*} param0 
     */
    set_cookie = ({ popupid, value, expiredays }) => {
        const today = new Date();
        today.setDate(today.getDate() + expiredays);
        document.cookie = popupid + "=" + encodeURIComponent(value) + "; path=/; expires=" + today.toGMTString() + ";"
    }//set_cookie
 
    /**
     * 
     */
    get_cookie = (popupid) => {
        const cookieData = document.cookie;
        console.log(cookieData);
        if (cookieData.indexOf(`${popupid}=done`) < 0) {
            console.log(`${popupid} 없음`);
            return false;
        }
        const $popup = document.querySelector(`[data-popupid="${popupid}"]`);
        if (!$popup) {
            throw new Error("can't find popup");
        }
        console.log(`${popupid} 있음`);
        this.on_close($popup);
        return true;
    }//get_cookie
 
    /**
     * 
     * @param {*} popupid 
     */
    delete_cookie = (popupid) => {
        console.log(popupid);
        const date = new Date("2099-01-01");
        document.cookie = popupid + "=" + encodeURIComponent("show"+ "; path=/; expires=" + date.toGMTString() + ";"
        console.log(document.cookie);
    }//delete_cookie
 
    /* ===================================== */
    /* ===================================== */
    /* ===================================== */
}//class:LayerPopup
cs

Draggable.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
export class Draggable {
    constructor() {
        this.$drag = null;
        this.posX = null;
        this.posY = null;
    }
    /**
     * 
     * @param {*} $drag 
     * @returns 
     */
    set_dom($drag) {
        this.$drag = $drag;
        return this;
    }//set_dom
 
    /**
     * 
     */
    init() {
        this.toggle_start_events(true);
    }//init
 
    /**
     * 
     */
    toggle_start_events = (add) => {
        const method = add ? 'addEventListener' : 'removeEventListener';
        this.$drag[method]("mousedown"this.on_start, { once: true });
        this.$drag[method]("touchstart"this.on_start, { once: true, passive: false });
    }//add_mouse_down
 
    /**
     * Mouse/Touch 이벤트에서 좌표를 추출하는 공통 함수
     */
    get_coords(e) {
        const result = { x: 0, y: 0 };
        if (e.type.startsWith('touch')) {
            result.x = e.touches[0].pageX;
            result.y = e.touches[0].pageY;
        } else {
            result.x = e.pageX;
            result.y = e.pageY;
        }
        return result;
    }
 
    /**
     * 
     * @param {*} e 
     */
    on_start = (e) => {
        const excludes = ["A""BUTTON""LABEL""INPUT"];
        if (excludes.includes(e.target.tagName)) {
            this.toggle_start_events(true);
            return;
        }
        // 터치 이벤트에서만 preventDefault를 실행하여 드래그에 집중하게 합니다.
        if (e.type === 'touchstart') {
            // e.cancelable 확인 후 실행 (안전장치)
            if (e.cancelable) e.preventDefault();
        }
 
        // 터치 시 스크롤 등 기본 동작 방지
        if (e.type === 'touchstart') { e.preventDefault(); }
 
        document.body.appendChild(this.$drag);
 
        const { x, y } = this.get_coords(e);
        const { left, top } = this.$drag.getBoundingClientRect();
 
        const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
        const scrollTop = window.scrollY || document.documentElement.scrollTop;
 
        this.posX = x - (left + scrollLeft);
        this.posY = y - (top + scrollTop);
 
        // Move & End 이벤트 바인딩
        window.addEventListener('mousemove'this.on_move);
        window.addEventListener('touchmove'this.on_move, { passive: false });
 
        window.addEventListener('mouseup'this.cancel, { once: true });
        window.addEventListener('touchend'this.cancel, { once: true });
        window.addEventListener('mouseleave'this.cancel, { once: true });
    }//on_start
 
    /**
     * 
     * @param {*} e 
     */
    on_move = (e) => {
        const { x, y } = this.get_coords(e);
        const { width, height } = this.$drag.getBoundingClientRect();
 
        const nowPosX = x - this.posX;
        const nowPosY = y - this.posY;
 
        const clamp = (val, min, max) => Math.max(min, Math.min(val, max));
 
        const maxX = document.documentElement.scrollWidth - width;
        const maxY = document.documentElement.scrollHeight - height;
 
        const finalX = clamp(nowPosX, 0, maxX);
        const finalY = clamp(nowPosY, 0, maxY);
 
        this.$drag.style.transform = `translate(${finalX}px, ${finalY}px)`;
    }//on_mouse_move
 
    /**
     * 
     * @param {*} e 
     */
    cancel = (e) => {
        window.removeEventListener('mousemove'this.on_move);
        window.removeEventListener('touchmove'this.on_move);
        this.toggle_start_events(true);
    }//cancel
}//Draggable
cs