如何将Chrome Manifest V3番茄钟扩展迁移至后台运行(2022)
问题描述
我正在开发一款基于Chromium、采用Manifest V3标准的番茄钟浏览器扩展。最初按普通网页方式编码,弹窗打开时定时器运行正常,但关闭弹窗后定时器就停止工作。我知道需要用background.js,但完全不清楚怎么把现有popup.js的逻辑迁移到后台,求具体代码修改方法。
我是Web开发新手,代码不够规范,只求实现功能。
现有代码
manifest.json
{ "name": "HyperFocused⚡", "version": "0.0.1", "manifest_version": 3, "icons": { "128": "icons/3.png" }, "action": { "default_icon": "icons/3.png", "default_popup": "popup.html" }, "permissions": [ "storage", "tabs" ], "minimum_chrome_version": "93", "background": { "service_worker": "background/background.js", "type": "module" } }
popup.js
var timerStarted = false; var playing = true; var minutes = 0; //Default Minutes var seconds = 25; //Default Seconds let pomoMin; //Pomodoro Minutes let pomoSec; //Pomodoro Seconds var myTime; const touchTimerElement = document.getElementById('touchTimer'); const startStopTimerElement = document.getElementById('startStopTimer'); function resetTime() { pomoMin = minutes; pomoSec = seconds; } function startStopText() { startStopTimerElement.innerHTML = timerStarted ? "STOP" : "START"; } function setTimerTime() { touchTimerElement.innerHTML = `${pomoMin}`.padStart(2, '0') + ':' + `${pomoSec}`.padStart(2, '0'); } function reset() { clearInterval(myTime); resetTime(); playing = true; setTimerTime(); } function countdownTimer() { if (!playing) return; if (pomoMin >= 0) { setTimerTime(); pomoSec--; if (pomoSec == -1) { pomoSec = 59; pomoMin--; } } else { // window.open( // // Just use the full URL if you need to open an external page // // url: chrome.runtime.getURL("google.com") // "breaktime.html", "_blank" // ); reset(); timerStarted = false; startStopText(); } } startStopTimerElement.onclick = function () { if (!timerStarted) { //start the timer; countdownTimer(); myTime = setInterval(countdownTimer, 1000); } else { reset(); } timerStarted = !timerStarted; startStopText(); } //pomoSec touchTimerElement.onclick = function () { if (timerStarted) playing = !playing; } startStopText(); resetTime(); setTimerTime();
popup.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="style.css"> <title>HyperFocused</title> </head> <body> <div class="title"> hyper<span class="fcsd">focused</span> </div> <!-- <button class="timerModes" value="Work Mode">Hello</button> --> <div id="touchTimer" class="timer"> </div> <div id="startStopTimer" class="stopButton"> </div> <script src="popup.js"></script> </body> </html>
解决方案
Manifest V3的popup关闭后会被销毁,定时器也会随之停止,必须把定时器逻辑移到后台Service Worker中,同时用chrome.storage.local持久化状态(防止Service Worker休眠丢失数据),通过消息通信实现popup和后台的交互。
1. 编写background/background.js
// 定时器初始状态 let timerState = { timerStarted: false, playing: true, minutes: 0, seconds: 25, pomoMin: 0, pomoSec: 25, intervalId: null }; // 从本地存储加载上次保存的状态 async function loadState() { const saved = await chrome.storage.local.get('timerState'); if (saved.timerState) { timerState = { ...timerState, ...saved.timerState }; // 如果之前定时器在运行,重启 if (timerState.timerStarted && timerState.playing) { startCountdown(); } } } // 将当前状态保存到本地存储 async function saveState() { await chrome.storage.local.set({ timerState }); } // 倒计时核心逻辑 function countdownTimer() { if (!timerState.playing) return; if (timerState.pomoMin >= 0) { timerState.pomoSec--; if (timerState.pomoSec === -1) { timerState.pomoSec = 59; timerState.pomoMin--; } saveState(); // 向popup发送时间更新 chrome.runtime.sendMessage({ type: 'TIME_UPDATE', data: { pomoMin: timerState.pomoMin, pomoSec: timerState.pomoSec } }); } else { // 倒计时结束,重置状态 resetTimer(); chrome.runtime.sendMessage({ type: 'TIMER_FINISHED' }); } } // 启动倒计时定时器 function startCountdown() { if (timerState.intervalId) clearInterval(timerState.intervalId); timerState.intervalId = setInterval(countdownTimer, 1000); saveState(); } // 重置定时器到初始状态 function resetTimer() { clearInterval(timerState.intervalId); timerState.timerStarted = false; timerState.playing = true; timerState.pomoMin = timerState.minutes; timerState.pomoSec = timerState.seconds; timerState.intervalId = null; saveState(); } // 接收来自popup的指令 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { switch (message.type) { case 'GET_STATE': sendResponse(timerState); break; case 'START_STOP': if (!timerState.timerStarted) { timerState.timerStarted = true; startCountdown(); } else { resetTimer(); } saveState(); sendResponse(timerState); break; case 'PAUSE_RESUME': if (timerState.timerStarted) { timerState.playing = !timerState.playing; if (timerState.playing) { startCountdown(); } else { clearInterval(timerState.intervalId); timerState.intervalId = null; } saveState(); } sendResponse(timerState); break; } }); // 初始化加载状态 loadState();
2. 修改popup.js
现在popup只负责UI展示和发送指令给后台,不再处理定时器逻辑:
const touchTimerElement = document.getElementById('touchTimer'); const startStopTimerElement = document.getElementById('startStopTimer'); // 根据后台返回的状态更新UI function updateUI(state) { startStopTimerElement.innerHTML = state.timerStarted ? "STOP" : "START"; touchTimerElement.innerHTML = `${state.pomoMin}`.padStart(2, '0') + ':' + `${state.pomoSec}`.padStart(2, '0'); } // 从后台获取当前状态并更新UI async function getStateAndUpdateUI() { const state = await chrome.runtime.sendMessage({ type: 'GET_STATE' }); updateUI(state); } // 启动/停止按钮点击事件 startStopTimerElement.onclick = async function () { const newState = await chrome.runtime.sendMessage({ type: 'START_STOP' }); updateUI(newState); } // 点击定时器区域暂停/恢复 touchTimerElement.onclick = async function () { const newState = await chrome.runtime.sendMessage({ type: 'PAUSE_RESUME' }); updateUI(newState); } // 监听后台发送的时间更新和结束通知 chrome.runtime.onMessage.addListener((message) => { if (message.type === 'TIME_UPDATE') { touchTimerElement.innerHTML = `${message.data.pomoMin}`.padStart(2, '0') + ':' + `${message.data.pomoSec}`.padStart(2, '0'); } else if (message.type === 'TIMER_FINISHED') { updateUI({ timerStarted: false, playing: true, pomoMin: 0, pomoSec: 25 }); alert('番茄钟结束!该休息了!'); } }); // 页面加载时获取初始状态 getStateAndUpdateUI();
3. 其他说明
- 无需修改
popup.html和manifest.json,保持原有内容即可 - 本地存储用于在Service Worker休眠/重启后恢复定时器状态,避免数据丢失
- 消息通信机制确保popup和后台的状态实时同步
内容的提问来源于stack exchange,提问作者Pranjal Chaplot
相关产品推荐
相关产品推荐

