iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

從 UI/UX 設計到前端實作:30 天打造專屬的任務學習計時器系列 第 17 篇

DAY 17|功能實作(五):計時結束跳出Modal 彈窗

  • 分享至 

  • xImage
  •  

做好計時器邏輯之後,我們要來實作計時結束後的互動——當使用者按下停止計時時,自動彈出 Modal 彈窗,讓使用者編輯當次備註、勾選任務完成狀態,並提供 「儲存紀錄」 與 「捨棄當次計時」 兩種彈性選擇。


1. HTML 結構:防誤觸的 Modal 設定

在 index.html 中,需要建立一個專用的 Modal結構。為了避免使用者不小心點到背景或按錯鍵導致剛計時完的資料消失,這裡使用了兩個關鍵的 Bootstrap 屬性:

  • data-bs-backdrop="static":鎖定背景,防止使用者點擊 Modal 外部時誤關視窗。

  • data-bs-keyboard="false":停用鍵盤 Esc 鍵關閉彈窗的功能。

<!-- 計時結束紀錄 Modal (核心結構) -->
<div class="modal fade" id="saveTimerModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title font-weight-bold"><i class="bi bi-check-circle-fill text-success me-2"></i>完成計時!</h5>
      </div>
      <div class="modal-body">
        <!-- 顯示累積時間 -->
        <h3 id="modal-focus-time" class="text-primary font-monospace text-center">00:00:00</h3>
        
        <!-- 輸入表單:備註與完成狀態 -->
        <textarea id="modal-note" class="form-control mb-3" placeholder="記錄一下完成項目吧..."></textarea>
        <div class="form-check">
          <input class="form-check-input" type="checkbox" id="modal-is-completed" />
          <label class="form-check-label" for="modal-is-completed">將此任務標記為已完成</label>
        </div>
      </div>
      <div class="modal-footer d-flex justify-content-between">
        <button type="button" class="btn btn-outline-danger" id="btn-discard-session">捨棄此次計時</button>
        <button type="button" class="btn btn-primary" id="btn-save-session">儲存紀錄</button>
      </div>
    </div>
  </div>
</div>

2. JavaScript 實作:Modal 控制與資料收集

在 all.js 中,我們改用 Bootstrap JS 原生 API (bootstrap.Modal) 來實作手動控制。整體運作流程如下:

[按下停止] ➔ 計算總秒數 ➔ 寫入並打開 Modal ➔ [使用者選擇: 儲存 或 捨棄] ➔ 重置主介面

步驟 1:初始化與元素選取

先取得頁面上的 DOM 元素,再使用 Bootstrap 的 Modal 類別建立一個可以操作的 Modal 物件,同時建立全域變數 currentSessionSeconds,用來暫存當次計時結束時的精確秒數。

// 初始化 Bootstrap Modal 物件
const saveTimerModalElement = document.querySelector("#saveTimerModal");
const saveTimerModal = new bootstrap.Modal(saveTimerModalElement);

// 暫存當次結束時累積的總秒數
let currentSessionSeconds = 0;

步驟 2:觸發結束計時 (stopTimer)

原本按下「停止」是直接重置 UI,現在改為先計算時間並判斷。若 計時小於 1 秒 (例如誤觸),就直接重置而不打擾使用者;反之,則將時間寫入 Modal 並呼叫 .show() 彈出視窗。

function stopTimer() {
  currentSessionSeconds = getCalculatedSeconds();

  // 防誤觸:未滿 1 秒不處理,直接重置
  if (currentSessionSeconds < 1) {
    resetTimerUI();
    return;
  }

  // 停止計時器並更新 Modal 數據
  clearInterval(timerInterval);
  isRunning = false;
  
  document.querySelector("#modal-focus-time").textContent = formatTime(currentSessionSeconds);
  document.querySelector("#modal-note").value = ""; // 清空上次輸入
  
  saveTimerModal.show(); // 跳出 Modal
}

步驟 3:實作「捨棄」與「儲存」邏輯

這兩個動作最終都會關閉彈窗並呼叫 resetTimerUI() 讓主介面歸零,差別在於 儲存時會將資料打包成 JSON 物件:

  • 捨棄紀錄 (discardSession):完全不做資料寫入,直接關閉 Modal 並重置。

  • 儲存紀錄 (saveSession):收集秒數、備註與勾選狀態,打包成獨立物件以利後續存入 IndexedDB。

// 捨棄紀錄:關閉彈窗並重置
function discardSession() {
  saveTimerModal.hide();
  resetTimerUI();
}

// 儲存紀錄:打包資料後關閉並重置
function saveSession() {
  const sessionData = {
    id: Date.now(),
    duration: currentSessionSeconds,
    note: document.querySelector("#modal-note").value.trim(),
    isCompleted: document.querySelector("#modal-is-completed").checked,
    createdAt: new Date().toISOString()
  };

  console.log("暫存紀錄成功:", sessionData);
  
  saveTimerModal.hide();
  resetTimerUI();
}

// 綁定事件
document.querySelector("#btn-discard-session").addEventListener("click", discardSession);
document.querySelector("#btn-save-session").addEventListener("click", saveSession);
document.querySelector("#btnStop").addEventListener("click", stopTimer);

Timer Modal


重點小結

  1. 使用者體驗防護:透過 data-bs-backdrop="static" 避免點到背景關閉視窗,確保使用者不會因誤觸丟失辛苦計時的資料。

  2. 乾淨的資料收集:使用 .trim() 處理字串,就算使用者完全不寫備註,也能回傳空字串 "" 避免資料出錯。

  3. 流程模組化:不管是點擊「儲存」還是「捨棄」,最後都統一呼叫 resetTimerUI(),保持程式碼簡潔與單一職責原則。


個人筆記:
JavaScript | 時間格式轉換:toISOString() 怎麼用?


上一篇
DAY 16|功能實作(四):精確計時(Date.now())與 background 防休眠
下一篇
DAY 18|任務管理(一):主任務與子任務的 State 管理與 CRUD 實作
系列文
從 UI/UX 設計到前端實作:30 天打造專屬的任務學習計時器 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言