본문 바로가기

PYTHON-BACK

# 24.10.22_ 마켓컬리 클론코딩4_선물특가 타이머

728x90

오늘은 마켓컬리 클린코딩 중에서 저번 프로젝트 기간에 해결하지 못했던 선물특가에 타이머 기능을 수정했습니다

 

해당 타이머 기능은 설정 시간을 지정하면 그 시간부터 1초씩 떨어져 00:00:00 까지 가는 코드였는데, 기존에는 새로고침을 하게 되면 떨어지는 시간이 다시 초기화되어 설정한 시간으로 되돌아가는 문제가 있었습니다.

 

궁극적으로 구현하고자 했던 부분은 저 초가 다 떨어지면 새로운 선물특가 상품 2개를 끌고오는것 까지 하려 했지만, 우선적으로 새로고침해도 흘렀던 시간은 유지되는 코드까지 구현해보았습니다.

 

코드는 html부분과 JavaScript 부분으로 나눠져 있으며

 

html 부분

<section class="special-offers">
        <h2 class="special-offers__title">&#127873; 선물특가</h2>
        <div class="special-offers__container">
            <div class="special-offers__left">
                <div class="special-offers__timer">
                    <div class="special-offers__timer-icon">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="20" height="20" preserveAspectRatio="xMidYMid meet">
                            <circle cx="18" cy="18" r="14" fill="rgb(189,118,255)" />
                            <!-- 시침-->
                            <line x1="18" y1="18" x2="18" y2="10" stroke="rgb(255,255,255)" stroke-width="2" stroke-linecap="round" />
                            <!-- 분침 -->
                            <line x1="18" y1="18" x2="26" y2="18" stroke="rgb(255,255,255)" stroke-width="2" stroke-linecap="round" />
                        </svg>
                    </div>
                    <div class="special-offers__timer-countdown" id="countdown">
                        <p>00:00:00</p> <!-- 초기 타이머 표시 -->
                    </div>
                </div>
                <br>
                <p class="small-gray-text">망설이면 늦어요!</p>
            </div>
            <div class="special-offers__right">
                <!-- 첫 번째 상품 -->
                <div class="special-offers__product">
                    <a href="#" class="special-offers__link" title="[선물세트] HBAF 매일 상품페이지">
                        <dl class="special-offers__product-item special-offers__discount">
                            <dd class="special-offers__product-thumbnail">
                                <img src="{% static 'images/hbaf.jpg' %}" alt="상품 이미지" aria-hidden="true">
                            </dd>
                            <button type="button" class="special-offers__button-cart" aria-label="장바구니로 이동">
                                🛒 담기
                            </button>
                            <dd class="special-offers__product-name">
                                <span aria-hidden="true">[주말특가][</span>선물세트<span aria-hidden="true">]</span> HBAF 매일 색다른 먼투썬 하루견과 6주 (20gX42봉)
                            </dd>
                            <dt class="a11yHidden">정상가</dt>
                            <dd class="special-offers__product-regPrice">
                                <del>34,900 원</del>
                            </dd>
                            <dt class="a11yHidden">할인가</dt>
                            <dd class="special-offers__product-disPrice">
                                <b class="special-offers__product-disRate">20%</b>
                                <ins>27,900 원</ins>
                            </dd>
                        </dl>
                    </a>
                </div>
                <!-- 두 번째 상품 -->
                <div class="special-offers__product">
                    <a href="#" class="special-offers__link" title="[선물세트] 설렁탕 상세페이지">
                        <dl class="special-offers__product-item special-offers__discount">
                            <dd class="special-offers__product-thumbnail">
                                <img src="{% static 'images/설렁탕.jpg' %}" alt="상품 이미지" aria-hidden="true">
                            </dd>
                            <button type="button" class="special-offers__button-cart" aria-label="장바구니로 이동">
                                🛒 담기
                            </button>
                            <dd class="special-offers__product-name">
                                <span aria-hidden="true">[주말특가][</span>선물세트<span aria-hidden="true">]</span> 신선설농탕 고기 설렁탕6입 세트
                            </dd>
                            <dt class="a11yHidden">정상가</dt>
                            <dd class="special-offers__product-regPrice">
                                <del>46,000 원</del>
                            </dd>
                            <dt class="a11yHidden">할인가</dt>
                            <dd class="special-offers__product-disPrice">
                                <b class="special-offers__product-disRate">20%</b>
                                <ins>36,800 원</ins>
                            </dd>
                        </dl>
                    </a>
                </div>
            </div>
        </div>
    </section>

 

JavaScript 부분

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            function updateCountdown(endTime) {
                const now = new Date();
                const timeRemaining = endTime - now;
                
                // 만약 시간이 이미 지났다면 종료
                if (timeRemaining <= 0) {
                    document.querySelector("#countdown p").textContent = '00:00:00';
                    return;
                }
                
                const days = Math.floor(timeRemaining / (1000 * 60 * 60 * 24));
                const hours = Math.floor((timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
                const minutes = Math.floor((timeRemaining % (1000 * 60 * 60)) / (1000 * 60));
                const seconds = Math.floor((timeRemaining % (1000 * 60)) / 1000);
    
                // '일'을 조건부로 출력
                const dayString = days > 0 ? `${days}일 ` : '';
                document.querySelector("#countdown p").textContent =
                    `${dayString}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
            }
    
            // 로컬 스토리지에서 종료 시간 불러오기
            let endTime = localStorage.getItem("endTime");
            
            // 종료 시간이 없거나 이미 종료된 경우 새로 설정
            if (!endTime || new Date(endTime) - new Date() <= 0) {
                endTime = new Date();
                endTime.setMinutes(endTime.getMinutes() + 120);
                localStorage.setItem("endTime", endTime); // 종료 시간을 로컬 스토리지에 저장
            } else {
                endTime = new Date(endTime);
            }
    
            // 1초마다 카운트다운 업데이트
            setInterval(() => updateCountdown(endTime), 1000);
        });
    </script>

 

1. DOMContentLoaded 이벤트

  • document.addEventListener("DOMContentLoaded", function() { 
  • 이 부분은 웹페이지가 온전히 로드되었을 때 실행될 함수를 지정합니다.
  • JavaScript가 실행되기 전에 HTML 요소들이 완전히 준비된 후에 타이머가 정상적으로 작동하도록 보장 하게 됩니다.

 

2. updateCountdown 함수

function updateCountdown(endTime) {
    const now = new Date(); // 현재 시간을 가져옴
    const timeRemaining = endTime - now; // 남은 시간 계산

    // 만약 시간이 이미 지났다면 종료
    if (timeRemaining <= 0) {
        document.querySelector("#countdown p").textContent = '00:00:00'; // 시간이 0초일 때 표시
        return;
    }

    // 날짜, 시간, 분, 초 계산
    const days = Math.floor(timeRemaining / (1000 * 60 * 60 * 24)); // 남은 시간에서 '일' 계산
    const hours = Math.floor((timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); // 남은 시간에서 '시간' 계산
    const minutes = Math.floor((timeRemaining % (1000 * 60 * 60)) / (1000 * 60)); // 남은 시간에서 '분' 계산
    const seconds = Math.floor((timeRemaining % (1000 * 60)) / 1000); // 남은 시간에서 '초' 계산

    // '일'을 조건부로 출력
    const dayString = days > 0 ? `${days}일 ` : ''; // 남은 '일'이 1 이상일 경우 일수 표시
    document.querySelector("#countdown p").textContent =
        `${dayString}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}

 

updateCountdown 함수는 남은 시간을 계산하여 HTML에 표시하는 역할을 하는데

  • now: 현재 시간을 Date 객체로 가져옵니다.
  • timeRemaining: 종료 시간(endTime)에서 현재 시간을 뺀 값을 계산하여 남은 시간을 밀리초로 나타냅니다.
  • 시간이 이미 지났다면 if (timeRemaining <= 0) 조건을 통해 타이머를 '00:00:00'으로 설정하고 업데이트를 멈추게 됩니다.
  • 시간 계산
    • days: 남은 시간을 하루(밀리초로 24시간)에 해당하는 값으로 나눈 후, Math.floor()로 내림하여 남은 '일'을 계산합니다.
    • hours: 남은 시간에서 '일' 단위를 제외한 후, 이를 시간으로 변환하여 '시간'을 계산합니다.
    • minutes: 남은 시간에서 '시간' 단위를 제외한 후 '분' 단위로 변환합니다.
    • seconds: 남은 시간에서 '분' 단위를 제외한 후 '초' 단위로 변환합니다.
  • 시간 포맷:
    • padStart(2, '0'): 숫자를 두 자리로 맞추기 위해 한 자리 숫자 앞에 '0'을 추가합니다. 예를 들어, '9'는 '09'로 표시됩니다.
    • 남은 '일'이 있을 경우 일수(dayString)를 표시하며, 없으면 빈 문자열로 처리됩니다.

 

3. 로컬 스토리지에서 종료 시간 불러오기

  • let endTime = localStorage.getItem("endTime");
  • localStorage.getItem() 을 사용해 로컬 스토리지에서 이전에 저장된 종료 시간을 가져오며
  • localStorage는 사용자가 웹 페이지를 다시 열 때도 데이터를 기억하기 위해 사용됩니다. 페이지가 새로 고침되거나 닫혀도 기존에 저장한 값을 유지합니다.

4. 종료 시간 설정 및 저장

if (!endTime || new Date(endTime) - new Date() <= 0) {
    endTime = new Date(); // 현재 시간을 가져옴
    endTime.setMinutes(endTime.getMinutes() + 120); // 120분 후로 종료 시간 설정
    localStorage.setItem("endTime", endTime); // 로컬 스토리지에 종료 시간 저장
} else {
    endTime = new Date(endTime); // 로컬 스토리지에서 불러온 값을 Date 객체로 변환
}

 

 

  • 만약 endTime이 없거나 이미 종료 시간이 지난 경우(new Date(endTime) - new Date() <= 0), 새로운 종료 시간을 설정해줍니다.
    • endTime = new Date();: 현재 시간부터 타이머를 시작
    • endTime.setMinutes(endTime.getMinutes() + 120);: 현재 시간으로부터 120분 후를 종료 시간으로 설정
    • localStorage.setItem("endTime", endTime);: 로컬 스토리지에 새 종료 시간을 저장
  • 종료 시간이 이미 설정되어 있고 유효하다면, 로컬 스토리지에서 불러온 시간을 Date 객체로 변환하여 다시 endTime에 할당 해줍니다.

 

5. 1초마다 카운트다운 업데이트

  • setInterval(() => updateCountdown(endTime), 1000);.
  • setInterval() 함수는 1초(1000밀리초)마다 updateCountdown 함수를 호출하여 타이머가 매초 업데이트되도록 설정하는 역항을 합니다.

 

html의 경우 지난 프로젝트와 거희 동일하여 따로 정리 하지는 않았습니다.

 

JavaScript 부분에서 제일 중점 적으로 수정했던거는 새로고침해도 흐른 시간이 유지되게 하는 것이였습니다.

 

위 코드에서 새로고침해도 흐른 시간이 유지되는 이유는 localStorage 를 사용했기 때문입니다.

 

  • localStorage.setItem("endTime", endTime);
  • 이 부분에서 타이머 시작 시간과 종료 시간을 기억시킵니다.
    • 처음 타이머 시작시, endTime 을 120분 후의 시간으로 설정하고, 그 값을 localStorage  에 저장합니다.
    • 이 방식으로 페이지가 새로고침 되어도 브라우저의 로컬 스토리지에 endTime 값이 저장되므로 종료 시간이 유지됩니다.
    • endTime  보는 법은 개발자 도구 F12 에서  Application 에 Local Storage를 들어가면 해당 사이트 주소가 나오는데 그 안에 서 endTime을 확인할 수 있습니다. (여기서 endTime 값이 새로고침할 때마다 바뀌면은 localStorage에 값이 저장된것이 아님)
  • let endTime = localStorage.getItem("endTime");
  • 새로 고침할 때 종료 시간 복원
    • 페이지 새로 고침될 때마다 JavaScript는 localStorage에서 endTime을 불러오게 됩니다.
    • 이전에 저장된 종료 시간이 있으면, 이를 Date 객체로 변환하고, 남은 시간을 계산해 계속해서 타이머가 진행되는 것입니다. 만약 조료 시간이 지나 있거나 endtime이 없는 상황에는 새로운 종료 시간을 설정합니다.
  • 시간 흐름 유지
    • 타이머가 새로 고침되었을 때도 localStorage에 저장된 기존의 endTime 을 사용해 남은 시간을 계산하므로, 새로 고침이 되어도 타이머가 계속 이어지게 됩니다.

 

이번에 구현한 타이머 흐름 정리

 

  • 처음 페이지가 열렸을 때:
    • 타이머가 시작되며, endTime이 120분 후의 값으로 설정됩니다.
    • 이 값은 localStorage에 저장 되어 지는 것입니다.
  • 페이지를 새로 고침했을 때:
    • JavaScript가 localStorage에서 endTime을 불러옵니다.
    • 불러온 endTime을 기준으로 남은 시간이 다시 계산되고, 타이머는 이어서 진행 되게 됩니다.

 

이를 통해 페이지가 새로 오픈(처음) 되었을때와 시간이 다 흐른 뒤 00:00:00 일때 새로고침시 를 제외하고는 지정된 시간이 초기화 되는 것을 제외하고는 해당 타이머가 초기화되는 경우는 없게 되는 것입니다.

 

 

이후에는 git 허블 통해 다른 팀원들의 코드랑 merge하는 작업을 진행중에 있습니다.

 

 

728x90