ブラウザ拡張機能でポモドーロタイマーを作る

# 概要

これは[初心者のブラウザ拡張機能 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/browser-extension)の17日目の記事です。

ポモドーロタイマーを作ってみます。

# ポモドーロタイマー

![timer](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/ba93ea56-ad19-a6c2-551a-ea255102d442.png)

# 具体的なコード

```json:manifest.json
{
    "browser_specific_settings": {
        "gecko": {
          "id": "the-town-pomodoro@example.com"
        }
    },
    "manifest_version": 2,
    "name": "init-extention",
    "version": "0.1",
  
    "description": "ポモドーロタイマー",
  
    "icons": {
      "48": "icons/icon-48.png"
    },
  
    "sidebar_action": {
      "default_title": "pomodoro",
      "default_panel": "timer.html",
      "default_icon": "icons/icon-48.png"
    },

    "permissions": [
      "tabs"
    ]
}
```

```javascript:timer.js
const WORK_TIME = 25 * 60;  // 25分
const BREAK_TIME = 5 * 60;  // 5分

let timeRemaining = WORK_TIME;
let totalTime = WORK_TIME;
let timerInterval;
let isRunning = false;
let isWorkSession = true;

const timerDisplay = document.getElementById('timeDisplay');
const progressCircle = document.getElementById('progressCircle');
const startBtn = document.getElementById('startBtn');
const resetBtn = document.getElementById('resetBtn');

// 円周の長さを正確に計算（2πr）
const R = 90
const circumference = 2 * Math.PI * R;
progressCircle.setAttribute('stroke-dasharray', circumference);

function formatTime(seconds) {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

function updateTimer() {
    timeRemaining--;
    
    if (timeRemaining < 0) {
        clearInterval(timerInterval);
        
        if (isWorkSession) {
            // 作業セッション終了、休憩開始
            timeRemaining = BREAK_TIME;
            totalTime = BREAK_TIME;
            isWorkSession = false;
        } else {
            // 休憩終了、作業セッション再開
            timeRemaining = WORK_TIME;
            totalTime = WORK_TIME;
            isWorkSession = true;
        }
        
        isRunning = false;
        startTimer();
        return;
    }

    timerDisplay.textContent = formatTime(timeRemaining);
    
    // プログレスバーの進捗を計算
    const progress = circumference * (timeRemaining / totalTime);
    progressCircle.style.strokeDashoffset = progress;
}

function startTimer() {
    if (!isRunning) {
        timerInterval = setInterval(updateTimer, 1000);
        startBtn.textContent = '一時停止';
        isRunning = true;
    } else {
        clearInterval(timerInterval);
        startBtn.textContent = '再開';
        isRunning = false;
    }
}

function resetTimer() {
    clearInterval(timerInterval);
    timeRemaining = WORK_TIME;
    totalTime = WORK_TIME;
    isWorkSession = true;
    isRunning = false;
    timerDisplay.textContent = formatTime(timeRemaining);
    progressCircle.style.strokeDashoffset = circumference;
    startBtn.textContent = '開始';
}

startBtn.addEventListener('click', startTimer);
resetBtn.addEventListener('click', resetTimer);
```

```timer.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="color-scheme" content="dark light">
    <title>ポモドーロタイマー</title>
    <link href="timer.css" rel="stylesheet" />
</head>
<body>
    <h1>ポモドーロタイマー</h1>
    <div id="timer">
        <svg viewBox="0 0 200 200">
            <circle 
                id="backgroundCircle"
                cx="100" 
                cy="100" 
                r="90" 
            />
            <circle 
                id="progressCircle"
                cx="100" 
                cy="100" 
                r="90" 
                stroke-dasharray="565.48" 
                stroke-dashoffset="565.48"
            />
        </svg>
        <div id="timeDisplay">25:00</div>
    </div>
    <div class="controls">
        <button id="startBtn">開始</button>
        <button id="resetBtn">リセット</button>
    </div>
    <script src="timer.js"></script>
</body>
</html>
```

```timer.css
body {
    width: 300px;
    text-align: center;
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    padding: 20px;
}
#timer {
    margin: 20px auto;
    width: 200px;
    height: 200px;
    position: relative;
}
#backgroundCircle {
    fill: none;
    stroke: #e0e0e0;
    stroke-width: 10;
}
#progressCircle {
    fill: none;
    stroke: #4CAF50;
    stroke-width: 10;
    transform: rotate(-90deg);
    transform-origin: center;
    transition: stroke-dashoffset 1s linear;
}
#timeDisplay {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: black;
    font-size: 36px;
    font-weight: bold;
}
.controls {
    display: flex;
    justify-content: center;
    gap: 10px;
}
button {
    padding: 10px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
button:hover {
    background-color: #45a049;
}

```