본문 바로가기
JavaScript

[JavaScript] 다중 Progress Bar와 전체 진행률 구현하기 (2) - 일시중지 및 삭제버튼 추가

by teamnova 2025. 2. 5.
728x90

 

 

안녕하세요 오늘은 지난 시간에 이어서 다중 Progrees Bar 에 일시정지 버튼, 그리고 삭제 버튼을 추가해보도록 하겠습니다. 

 

지난 포스팅은 아래 링크에서 확인하실 수 있습니다 

 

[JavaScript] 사용자 입력 기반 다중 Progress Bar와 전체 진행률 구현하기

안녕하세요 오늘은 자바스크립트와 html 을 이용해 Progress Bar를 만들어보도록 하겠습니다.   프로그레스바는 사용자가 진행 상황을 시각적으로 쉽게 파악할 수 있도록 도와주는 UI 요소로, 다

stickode.tistory.com

 

 

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>진행률 표시줄</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }
    .progress-container {
      margin-bottom: 20px;
      border: 1px solid #ddd;
      padding: 10px;
      border-radius: 5px;
      position: relative;
    }
    .progress-bar {
      width: 100%;
      background-color: #f3f3f3;
      border-radius: 5px;
      overflow: hidden;
      height: 20px;
      margin: 10px 0;
    }
    .progress-bar div {
      height: 100%;
      width: 0;
      background-color: #4caf50;
      transition: width 0.2s;
    }
    .label {
      margin-bottom: 5px;
      font-weight: bold;
    }
    .time-input {
      margin-top: 5px;
      width: 100%;
      padding: 5px;
      box-sizing: border-box;
    }
    .start-button {
      margin: 20px 5px 0 0;
      padding: 10px 20px;
      font-size: 16px;
      background-color: #4caf50;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    .start-button:hover {
      background-color: #45a049;
    }
    .control-buttons {
      margin-top: 10px;
    }
    .control-buttons button {
      margin-right: 5px;
      padding: 5px 10px;
      font-size: 14px;
      border: none;
      border-radius: 3px;
      cursor: pointer;
    }
    .pause-button {
      background-color: #ffc107;
      color: white;
    }
    .cancel-button {
      background-color: #9c27b0;
      color: white;
    }
    .delete-button {
      background-color: #e91e63;
      color: white;
      position: absolute;
      top: 10px;
      right: 10px;
    }
    .pause-button:hover {
      background-color: #ffb300;
    }
    .cancel-button:hover {
      background-color: #7b1fa2;
    }
    .delete-button:hover {
      background-color: #c2185b;
    }
    .progress-container.paused .progress-bar div {
      background-color: #ff9800;
    }
  </style>
</head>
<body>
  <h1>진행률 표시줄</h1>

  <div id="progress-bars"></div>

  <button class="start-button" id="start-button">시작</button>
  
  <div class="progress-container">
    <div class="label">전체 진행률:</div>
    <div class="progress-bar" id="overall-bar">
      <div></div>
    </div>
  </div>

  <script>
    document.addEventListener("DOMContentLoaded", () => {
      const numItems = 5;
      const progressBarsContainer = document.getElementById("progress-bars");
      const overallBar = document.getElementById("overall-bar").children[0];
      const startButton = document.getElementById("start-button");
      let completedItems = 0;
      const progressStates = [];

      function createProgressBar(index) {
        const container = document.createElement("div");
        container.className = "progress-container";

        const label = document.createElement("div");
        label.className = "label";
        label.innerText = `항목 ${index + 1}`;

        const progressBar = document.createElement("div");
        progressBar.className = "progress-bar";
        const progress = document.createElement("div");
        progressBar.appendChild(progress);

        const input = document.createElement("input");
        input.type = "number";
        input.className = "time-input";
        input.placeholder = "시간(초)을 입력하세요";
        input.min = 1;

        const controlButtons = document.createElement("div");
        controlButtons.className = "control-buttons";

        const pauseButton = document.createElement("button");
        pauseButton.className = "pause-button";
        pauseButton.innerText = "일시중지";

        const cancelButton = document.createElement("button");
        cancelButton.className = "cancel-button";
        cancelButton.innerText = "취소";

        controlButtons.appendChild(pauseButton);
        controlButtons.appendChild(cancelButton);

        // 삭제 버튼 추가
        const deleteButton = document.createElement("button");
        deleteButton.className = "delete-button";
        deleteButton.innerText = "삭제";
        container.appendChild(deleteButton);

        container.appendChild(label);
        container.appendChild(progressBar);
        container.appendChild(input);
        container.appendChild(controlButtons);
        progressBarsContainer.appendChild(container);

        // 상태 객체 초기화
        const state = {
          progress: progress,
          input: input,
          interval: null,
          startTime: null,
          endTime: null,
          duration: 0,
          paused: false,
          remainingTime: 0
        };
        progressStates.push(state);

        // 일시중지/재개 버튼 이벤트
        pauseButton.addEventListener("click", () => {
          if (!state.interval) return; // 진행 중이 아닌 경우

          if (!state.paused) {
            // 일시중지
            clearInterval(state.interval);
            state.interval = null;
            state.paused = true;
            const now = Date.now();
            state.remainingTime = (state.endTime - now) / 1000;
            pauseButton.innerText = "재개";
            container.classList.add("paused");
            updateOverallProgress();
          } else {
            // 재개
            state.paused = false;
            state.startTime = Date.now();
            state.endTime = state.startTime + state.remainingTime * 1000;
            state.interval = setInterval(() => {
              const now = Date.now();
              const percentage = Math.min(((now - state.startTime) / (state.endTime - state.startTime)) * 100, 100);
              state.progress.style.width = percentage + "%";

              if (percentage >= 100) {
                clearInterval(state.interval);
                state.interval = null;
                completedItems++;
                updateOverallProgress();
              } else {
                updateOverallProgress();
              }
            }, 100);
            pauseButton.innerText = "일시중지";
            container.classList.remove("paused");
            updateOverallProgress();
          }
        });

        // 취소 버튼 이벤트
        cancelButton.addEventListener("click", () => {
          if (state.interval) {
            clearInterval(state.interval);
            state.interval = null;
          }
          const wasCompleted = parseFloat(state.progress.style.width) === 100;
          state.progress.style.width = "0%";
          state.input.value = "";
          if (wasCompleted) {
            completedItems--;
          }
          state.paused = false;
          pauseButton.innerText = "일시중지";
          container.classList.remove("paused");
          updateOverallProgress();
        });

        // 삭제 버튼 이벤트
        deleteButton.addEventListener("click", () => {
          if (state.interval) {
            clearInterval(state.interval);
          }
          const wasCompleted = parseFloat(state.progress.style.width) === 100;
          if (wasCompleted) {
            completedItems--;
          }
          progressBarsContainer.removeChild(container);
          const progressIndex = progressStates.indexOf(state);
          if (progressIndex > -1) {
            progressStates.splice(progressIndex, 1);
            updateOverallProgress();
          }
        });

        return state;
      }

      function updateOverallProgress() {
        const activeStates = progressStates.filter(state => state.duration > 0 && parseFloat(state.progress.style.width) > 0);
        const total = activeStates.length;
        if (total === 0) {
          overallBar.style.width = "0%";
          return;
        }
        const totalPercentage = activeStates.reduce((acc, state) => {
          const width = parseFloat(state.progress.style.width);
          return acc + (isNaN(width) ? 0 : width);
        }, 0);
        const overallPercentage = totalPercentage / total;
        overallBar.style.width = `${overallPercentage}%`;
      }

      function startProgress() {
        progressStates.forEach((state, index) => {
          const seconds = parseInt(state.input.value, 10);
          if (isNaN(seconds) || seconds < 1) {
            return;
          }

          if (state.interval || parseFloat(state.progress.style.width) === 100) return;

          state.duration = seconds;
          state.startTime = Date.now();
          state.endTime = state.startTime + state.duration * 1000;
          state.progress.style.width = "0%";

          state.interval = setInterval(() => {
            const now = Date.now();
            const percentage = Math.min(((now - state.startTime) / (state.endTime - state.startTime)) * 100, 100);
            state.progress.style.width = percentage + "%";

            if (percentage >= 100) {
              clearInterval(state.interval);
              state.interval = null;
              completedItems++;
              updateOverallProgress();
            } else {
              updateOverallProgress();
            }
          }, 100);
        });
        updateOverallProgress();
      }

      startButton.addEventListener("click", startProgress);

      // 초기 진행률 표시줄 생성
      for (let i = 0; i < numItems; i++) {
        createProgressBar(i);
      }
    });
  </script>
</body>
</html>

 

위 코드의 흐름은 다음과 같습니다. 

 

  • 시간 입력 및 시작
    -
    각 항목의 입력란에 원하는 시간을 초 단위로 입력합니다.
    - "시작" 버튼을 클릭하면, 입력된 시간에 해당하는 항목의 진행률이 시작됩니다
  • 일시정지 
    - 진행 중인 항목의 "일시중지" 버튼을 클릭하면 해당 진행이 일시정지됩니다.
  • 삭제 
    - 각 진행률 표시줄의 오른쪽 상단에 있는 "삭제" 버튼을 클릭하면 해당 항목이 전체 목록에서 제거됩니다.