You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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"
    }
}
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();
<!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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:10:50