gi_dor

팝업 모달 & 24시간 보지 않기 with JS , Cookie 본문

Language/JavaScript

팝업 모달 & 24시간 보지 않기 with JS , Cookie

기돌 2026. 2. 26. 21:42

개발 중인 미니 프로젝트는 만들어가는 단계 이므로 배포 이후 사용자가 미구현된 버튼을 클릭하고 안되는 기능이나
데이터가 없어서 작동하지 않는 검색조건이 있을수 있어 사용자가 사이트를 이탈하는 것을 방지하기 위해

메인 페이지 진입 시 '서비스 업데이트 로드맵'을 안내하는 팝업 모달 & 검색시 없는 데이터 안내문 ' 을 띄우기로 결정
다만, 매번 접속할 때마다 팝업이 뜨면 피로감을 줄 수 있으므로 '24시간 동안 보지 않기' 기능을 쿠키(Cookie)를 활용해 구현했음

기능 정의

  • 사용자가 웹 사이트에 방문하면 공지사항 팝업이 나타난다
  • 팝업 안에는 24시간 다시보지 않기 체크박스 있다
  • 체크박스를 선택하지 않고 팝업을 닫으면 메인 페이지 재방문시 팝업 발생
  • 체크박스 선택후 팝업을 닫으면 메인 페이지 재방문시 팝업 미 발생

 

모달html

  • data-bs-dismiss="modal" Bootstrap이 제공하는 기본 닫기 기능
  • onclick="closeNoticeModal() 닫기 버튼 누를때 쿠키 생성 처리할 JS 함수
<!-- src/main/resources/templates/common/noticeModal.html -->
<!DOCTYPE html>
<html xmlns:th="[http://www.thymeleaf.org](http://www.thymeleaf.org)">
<div th:fragment="noticeModal">
    <div class="modal fade" id="noticeModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content border-0 shadow-lg">
                <!-- 모달 헤더 -->
                <div class="modal-header bg-dark text-white">
                    <h5 class="modal-title fw-bold">
                        <i class="bi bi-megaphone-fill me-2"></i>서비스 업데이트 로드맵 안내
                    </h5>
                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                </div>

                <!-- 모달 바디 (로드맵 내용 생략) -->
                <div class="modal-body p-3">
                   <!-- ... 업데이트 예정 기능 목록 ... -->
                </div>

                <!-- 모달 푸터 (24시간 보지 않기) -->
                <div class="modal-footer d-flex justify-content-between bg-light">
                    <div class="form-check mb-0">
                        <input class="form-check-input" type="checkbox" id="hideFor24Hours">
                        <label class="form-check-label text-muted small mt-1" for="hideFor24Hours">
                            24시간 동안 보지 않기
                        </label>
                    </div>
                    <!-- 닫기 버튼 클릭 시 이벤트 발생 -->
                    <button type="button" class="btn btn-secondary btn-sm px-4" data-bs-dismiss="modal" onclick="closeNoticeModal()">닫기</button>
                </div>
            </div>
        </div>
    </div>
</div>
</html>

메인 페이지 html

<!-- src/main/resources/templates/index.html -->
<body>
    <!-- 1. 네비게이션 바 -->
    <nav th:replace="~{common/navbar :: navbar}"></nav>

    <!-- 2. 메인  영역 -->
    <main>

    </main>

    <!-- 3. 공지사항 모달 Fragment 삽입 -->
    <div th:replace="~{common/noticeModal :: noticeModal}"></div>

    <!-- 4. 푸터 -->
    <footer th:replace="~{common/footer :: footer}"></footer>

    <!-- Bootstrap JS (반드시 모달 제어 스크립트보다 위에 위치해야 함!!!!!!) -->
    <script src="[https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js](https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js)"></script>

    <!-- 모달 팝업 제어용 커스텀 JS 영역 -->
</body>

모달 팝업 JS

// 페이지 로드 다되면 쿠키 확인후에 팝업 출력 결정
window.onload = function () {
    // 쿠키 문자열에 'hideNotice=true'가 포함되어 있는지 검사
    if(document.cookie.includes("hideNotice=true")) {
        // 쿠키가 존재하면 아무 동작도 하지 않음 (팝업 숨김)
    } else {
        // 쿠키가 없으면 Bootstrap 모달 객체를 생성
        let noticeModal = new bootstrap.Modal(document.getElementById('noticeModal'));
        noticeModal.show();    // 화면에 표시
    }
}

// 닫기 버튼 클릭 시 동작
function closeNoticeModal() {
    let checkbox = document.getElementById('hideFor24Hours');
    if(checkbox.checked) {
        // 체크박스가 선택된 상태로 닫으면 유효기간 1일짜리 쿠키 생성
        setCookie("hideNotice", "true", 1);
    }
}

// 재사용 가능한 쿠키 생성됨
/**
 * @param {string} name - 생성할 쿠키의 이름
 * @param {string} value - 쿠키에 저장할 값
 * @param {number} days - 쿠키의 유효기간 (일 단위)
 */
function setCookie(name, value, days) {
    let todayDate = new Date();
    // 일(days) 단위의 시간을 밀리초(ms)로 변환
    let duration = days * 24 * 60 * 60 * 1000;

    // 현재 시간에 계산된 밀리초를 더하여 만료 시점을 설정
    todayDate.setTime(todayDate.getTime() + duration);
    let expires = todayDate.toUTCString();

    // 브라우저에 쿠키 저장 (path=/ 옵션으로 사이트 전체에서 쿠키 공유)
    document.cookie = name + "=" + value + "; expires=" + expires + "; path=/";
}
  • 페이지가 열릴때 JS에서 브라우저의 쿠키 저장소를 확인해 팝업그만보기 쿠키가 있는지 확인 ( Ex. hideNotice )
  • 팝업 결정 여부
  • 팝업 생성
    • 24시간 보지 않기 체크 후 닫기 누르면 JS는 브라우저에 hideNotice=true

 

사실 여기까지는 해피엔딩이어씀


includes() 방식과 getCookie()의 필요성

예상치 못한 쿠키 이름 충돌에 매우 취약하다
예를 들어 테스트를 위해 추가된 쿠키 :do_not_hideNotice=true
이제 브라우저에 저장된 document.cookie문자열은 아래와 같은 형태가 된다
"abcdCookie=true; do_not_hideNotice=true; sdaCookie=txxxxxxx; hideNotice=true"

이 상태에서, 페이지가 로드될 때 includes() 코드가 실행되면

  • 코드: if (document.cookie.includes("hideNotice=true")) { ... }
  • 동작 : JavaScript는 전체 문자열 안에서 "hideNotice=true" 라는 텍스트를 찾는다
  • 결과 : "do_not_hideNotice=true" 라는 문자열 안에 "hideNotice=true"가 포함되어 있으므로 해당 조건은 true를 반환

getCookie() 함수는 해당 문제를 해결할수 있다.

  • document.cookie 문자열을 세미콜론(;)으로 쪼개어, ["...", "do_not_hideNotice=true"] 와 같이 각 쿠키를 분리
  • 정확한 시작점 확인: ("do_not_hideNotice=true")가 찾으려는 정확한 키와 등호 ("hideNotice=") 로 시작하는지(indexOf(...) === 0)를 검사
  • "do_not_hideNotice=true"는 "hideNotice="로 시작하지 않으므로 무시된다
  • 오직 정확히 "hideNotice=true" 라는 조각을 찾았을 때만 값을 반환

왜 getCookie()를 사용해야 하는가?

  • includes() : 빠르고 간편하다 (최고!) 다른 쿠키의 이름에 내가 찾는 문자열이 '포함'되는 경우 오작동할 수 있다
  • getCookie(): 코드는 조금 더 길고 쿠키를 구분하고 정확한 매개변수를 기준으로 값을 찾아오므로 이름 충돌에서 안전
  window.onload = function () {
    if(!getCookie('hideSearchModal')) {
        let noticeModal = new bootstrap.Modal(document.getElementById('noticeModal'));
        noticeModal.show();
  }


// 쿠키 확인 함수
function getCookie(name) {
    let nameCookie = name + "=";
    console.log('nameCookie',nameCookie)    // "hideSearchModal="

    // (2) ['hideNotice=true', ' hideSearchModal=true']
    let splitCookie = document.cookie.split(';');
    console.log('splitCookie',splitCookie)

    for(let i = 0; i < splitCookie.length; i++) {
        let cookie = splitCookie[i];
        console.log('splitCookie[i]',cookie)    // hideNotice=true ,  hideSearchModal=true

        // 두번째 쿠키 부터는 앞에 ' hideSearchModal=true' 이런식으로 공백이있다
        while (cookie.charAt(0) === ' ') {
            cookie = cookie.substring(1, cookie.length);
            console.log('while cookie',cookie)
        }

        if (cookie.indexOf(nameCookie) === 0) {
            console.log('nameCookie.length',nameCookie.length)  // 16
            console.log('cookie.length',cookie.length)          // 20
            console.log('cookieString',cookie)                  // hideSearchModal=true
            return  cookie.substring(nameCookie.length, cookie.length);
        }
    }
    return null;
}

현재 내 무신사 사이트내 쿠키만 37개인데 막말로 문자열 같은 쿠키 있을수도 있잖아!!!!!!

728x90

'Language > JavaScript' 카테고리의 다른 글

04. Ajax  (0) 2024.01.17
03. DOM  (0) 2024.01.15
02. 이벤트  (2) 2024.01.12