在本部分中,我们将使用在第 1 部分:理论中学到的 API 创建一个 Pomodoro 计时器扩展。样式设计将使用 CSS 完成。本项目的完整代码可在 GitHub 上找到。
Manifest 和 Popup
大家现在都知道,在构建扩展时,创建的第一个文件是 manifest 文件。
创建一个新的 manifest.json
文件。
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json
{ "manifest_version": 3, "name": "Pomodoro Timer", "version": "1.0", "description": "Assists you to focus and get things done", "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }, "action": { "default_icon": "icon.png", "default_title": "Pomodoro Timer", "default_popup": "popup/popup.html" } }
现在我们已经设置好扩展名,让我们创建弹出页面。popup.html
文件放在 popup
文件夹中,为我们的项目添加结构。
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json ┣ 📂 popup ┃ ┣ 📄 popup.html
在前面的代码中,我们定义了弹出页面的结构。它与 pop.css
文件相连,用于设计样式,与 pop.js
文件相连,用于交互。
让我们添加一些样式,并创建与 popup.html
链接的 popup.css 文件。
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json ┣ 📂 popup ┣ 📄 popup.css
body { height: 400px; width: 300px; } .header { display: flex; justify-content: center; height: 40px; }
在浏览器上重新载入扩展页面并点击弹出窗口,弹出窗口会显示 popup.html
。
任务列表功能
通过任务列表功能,我们可以添加和删除任务。
添加任务
在 popup.html
中
在 popup.js
中
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json ┣ 📂 popup ┣ 📄 popup.css ┣ 📄 popup.js
const addTaskBtn = document.getElementById('add-task-btn') addTaskBtn.addEventListener('click', () => addTask()) function addTask() { const taskRow = document.createElement('div') // Create text input const text = document.createElement('input') text.type = 'text' text.placeholder = 'Enter a task..' // Create delete button const deleteBtn = document.createElement('input') deleteBtn.type = 'button' deleteBtn.value = 'X' // append input elements to taskRow taskRow.appendChild(text) taskRow.appendChild(deleteBtn) // append taskRow to taskContainer const taskContainer = document.getElementById('task-container') taskContainer.appendChild(taskRow) }
在前面的代码中,我们通过 Add Task
按钮的 id
选择了该按钮,添加了一个 click
事件监听器和一个回调函数,该函数用于在用户界面中添加一个新任务。
删除任务
在 popup.js
中
- const addTaskBtn = document.getElementById('add-task-btn') addTaskBtn.addEventListener('click', () => addTask()) - function addTask() { const taskRow = document.createElement('div') // Create text input - const text = document.createElement('input') text.type = 'text' text.placeholder = 'Enter a task..' // Create delete button - const deleteBtn = document.createElement('input') deleteBtn.type = 'button' deleteBtn.value = 'X' // append input elements to taskRow taskRow.appendChild(text) taskRow.appendChild(deleteBtn) // append taskRow to taskContainer - const taskContainer = document.getElementById('task-container') taskContainer.appendChild(taskRow) }
// array to store tasks let tasks = [] const addTaskBtn = document.getElementById('add-task-btn') addTaskBtn.addEventListener('click', () => addTask()) // render tasks function renderTask(taskNum) { const taskRow = document.createElement('div') // Create text input const text = document.createElement('input') text.type = 'text' text.placeholder = 'Enter a task..' //Set and track input values of tasks in the array text.value = tasks[taskNum] text.addEventListener('change', () => { tasks[tasksNum] = text.value }) // Create delete button const deleteBtn = document.createElement('input') deleteBtn.type = 'button' deleteBtn.value = 'X' // delete task deleteBtn.addEventListener('click', () => { deleteTask(taskNum) }) // append input elements to taskRow taskRow.appendChild(text) taskRow.appendChild(deleteBtn) // append taskRow to taskContainer const taskContainer = document.getElementById('task-container') taskContainer.appendChild(taskRow) } function addTask() { const tasksNum = tasks.length // add tasks to array tasks.push('') renderTask(tasksNum) } // delete and re-render tasks after mutation function deleteTask(tasksNum) { tasks.splice(tasksNum, 1) renderTasks() } function renderTasks() { const taskContainer = document.getElementById('task-container') taskContainer.textContent = '' tasks.forEach((taskText, tasksNum) => { renderTask(tasksNum) }) }
在前面的代码中,我们对 popup.js
文件进行了重大修改。让我们来了解一下发生了什么:
- 基本上,我们正在添加和删除任务
- 创建一个数组(
tasks
)来存储任务 - 当点击
Add Task
按钮时,rendTask()
函数会创建一个新任务并将其呈现在 DOM(文档对象模型)上。 -
addTask()
函数是添加任务按钮的事件处理程序 - 当点击删除任务按钮(
X
)时,deleteTask()
函数会删除任务。 - 每当删除一个任务时,
renderTasks()
函数都会更新任务数组,即重新渲染用户界面。
现在,如果我们检查扩展,就可以添加和删除任务,但数据不是持久的,我们需要实现存储。
存储任务
首先,我们在 manifest.json
中设置了使用存储 API 所需的权限。
{ "manifest_version": 3, "name": "Pomodoro Timer", "version": "1.0", "description": "Assists you to focus and get things done", "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }, "action": { "default_icon": "icon.png", "default_title": "Pomodoro Timer", "default_popup": "popup/popup.html" }, + "permissions": ["storage"] }
在 popup.js
中
// array to store tasks let tasks = [] const addTaskBtn = document.getElementById('add-task-btn') addTaskBtn.addEventListener('click', () => addTask()) // set default storage value for the tasks chrome.storage.sync.get(['tasks'], (res) => { tasks = res.tasks ? res.tasks : [] renderTasks() }) // save tasks function saveTasks() { chrome.storage.sync.set({ tasks: tasks, }) } // render tasks function renderTask(taskNum) { const taskRow = document.createElement('div') // Create text input const text = document.createElement('input') text.type = 'text' text.placeholder = 'Enter a task..' //Set and track input values of tasks in the array text.value = tasks[taskNum] text.addEventListener('change', () => { tasks[taskNum] = text.value // call saveTask whenever a value changes saveTasks() }) .... function addTask() { const tasksNum = tasks.length // add tasks to array tasks.push('') renderTask(tasksNum) saveTasks() } // delete and re-render tasks after mutation function deleteTask(tasksNum) { tasks.splice(tasksNum, 1) renderTasks() saveTasks() }
在前面的代码中,我们使用 Chrome 浏览器的存储 API 来存储扩展数据。
- 如果任务数组中没有要渲染的任务,扩展的默认数据最初会设置为空数组。
saveTasks()
函数将我们的任务数组存储在存储 API 中。- 在
renderTask()
中,每当添加或删除一个任务时,都会通过saveTasks()
进行保存,addTask()
和deleteTask()
也是如此。
任务功能已完成;我们可以删除、添加和存储任务。
计时器功能
计时器功能要求我们创建一个后台脚本,并使用警报和通知在计时器时间到时通知用户。
启动和暂停计时器
让我们在 manifest.json
中设置所需的权限。
{ "manifest_version": 3, "name": "Pomodoro Timer", "version": "1.0", "description": "Assists you to focus and get things done", "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }, "action": { "default_icon": "icon.png", "default_title": "Pomodoro Timer", "default_popup": "popup/popup.html" }, + "permissions": ["storage", "alarms", "notifications"], + "background": { "service_worker": "background.js" } }
为我们的背景脚本创建 background.js
文件。
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json ┣ 📂 popup ┣ 📄 popup.css ┣ 📄 popup.js ┣ 📄 background.js
// create an alarm to notify user when time is up chrome.alarms.create("pomodoroTimer", { periodInMinutes: 1 / 60 }) // alarm listener chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "pomodoroTimer") { chrome.storage.local.get(["timer", "isRunning"], (res) => { if (res.isRunning) { let timer = res.timer + 1 console.log(timer) chrome.storage.local.set({ timer, }) } }) } }) // storage to set and track timer variables on load chrome.storage.local.get(["timer", "isRunning"], (res) => { chrome.storage.local.set({ timer: "timer" in res ? res.timer : 0, isRunning: "isRunning" in res ? res.isRunning : false, }) })
在 popup.js
中
// array to store tasks let tasks = [] // Start Timer Button + const startTimerBtn = document.getElementById("start-timer-btn"); startTimerBtn.addEventListener("click", () => { chrome.storage.local.get(["isRunning"], (res) => { chrome.storage.local.set({ isRunning: !res.isRunning, }, () => { startTimerBtn.textContent = !res.isRunning ? "Pause Timer" : "Start Timer" }) }) })
在前面的代码中:
- 我们设置了一个闹钟,当点击开始计时器按钮时触发。
timer
和isRunning
变量用于跟踪时间和计时器的状态,它们作为应用程序初始数据存储在存储器中。- 我们监听(
onAlarm.addListener
)警报,并在isRunning
为true
时递增timer
,然后将timer
记录到控制台。 - 最后,在
popup.js
中,我们监听 Start Timer 按钮的click
事件,并获取当前的isRunning
值。如果当前值为true
,则设置为false
,计时器暂停;如果当前值为false
,则设置为true
,重置计时器。
重置计时器
现在,让我们来开发重置计时器功能。创建重置按钮 popup.html
的标记。
在 popup.js
中
// Reset Timer Button const resetTimerBtn = document.getElementById("reset-timer-btn") resetTimerBtn.addEventListener("click", () => { chrome.storage.local.set({ // reset variables timer: 0, isRunning: false }, () => { // reset start button text-content startTimerBtn.textContent = "Start Timer" }) })
在前面的代码中,我们执行了以下操作:
- 通过
id
选择 DOM 上的Reset Timer
按钮。 - 为按钮添加一个
click
事件监听器,并使用回调函数重置timer
和存储中的isRunning
变量。 - 最后,根据当前为 “暂停计时器” 的假设,将
Start Timer
按钮文本设置为字符串 “Start Timer”。
在弹出窗口中显示时间
到目前为止,我们一直在控制台上记录计时器的值。让我们在弹出页面上显示时间。
在 popup.html
中
在 popup.js
中
// array to store tasks let tasks = []; const time = document.getElementById("time"); // Update time every 1sec function updateTime() { chrome.storage.local.get(["timer"], (res) => { const time = document.getElementById("time") // get no. of minutes & secs const minutes = `${25 - Math.ceil(res.timer / 60)}`.padStart(2, "0"); let seconds = "00"; if (res.timer % 60 != 0) { seconds = `${60 -res.timer % 60}`.padStart(2, "0"); } // show minutes & secs on UI time.textContent = `${minutes}:${seconds}` }) } updateTime() setInterval(updateTime, 1000) // Start Timer Button
在前面的代码中,我们在弹出页面上显示时间,并在点击任何影响计时器的按钮时更新时间。让我们更好地理解这段代码:
-
updateTime()
函数中进行了一些数学运算;计时器的值是从存储空间中获取的。 - 获取计时器的分钟数(计时器应从 25 分钟开始倒计时)并存储在
minute
变量中。25 - res.timer / 60
– 例如,如果我们的计时器值(res.timer)是 120 秒,120 / 60 = 2,那么25 - 2 = 23
,即时钟将剩下 23 分钟。 - 要获得秒数,我们要将计时器值(
res.timer
)除以60
。 minutes
和seconds
的值通过time.textContent
显示在用户界面上。-
updateTime()
会在弹出窗口加载时自动调用,并通过setInterval
每1sec
调用一次。
现在可以在弹出窗口中看到时间了。
发送通知
现在,让我们设置通知,以便在时间到时通知用户。
在 background.js
中
// alarm listener chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "pomodoroTimer") { chrome.storage.local.get(["timer", "isRunning"], (res) => { if (res.isRunning) { let timer = res.timer + 1 let isRunning = true if(timer === 25) { this.registration.showNotification('Pomodoro Timer', { body: "25 minutes has passed", icon: "icon.png" }) timer = 0 isRunning = false } chrome.storage.local.set({ timer, isRunning, }) } }) } })
在前面的代码中,我们使用 if
语句检查计时器是否达到 25 分钟,然后注册一个通知。计时器到期后,timer
值重置为 0
,isRunning
为 false
,从而暂停计时器。要测试此功能,请将计时器的默认值设为 10secs
(记住,这只是为了测试目的,所以我们不会等待 25 分钟)。在上述代码的 if
语句中,将计时器值改为 10secs
– if(timer === 10)
。现在重新启动计时器,10secs
后,您将看到一条通知。
选项页面
现在,我们已经具备了该扩展的基本功能:我们可以启动计时器、暂停计时器、重置计时器以及删除和添加任务。现在,让我们使扩展功能更具定制性,这样用户就可以根据自己的需求进行定制–有些用户可能希望专注于更长或更短的时间段。我们必须创建一个选项页面,以便用户配置扩展。用户可以将会话的最长时间设置为 1 小时(60 分钟),最短时间设置为 1 分钟。
设置和存储选项
在 manifest.json 中添加 options_page
文件。
{ "manifest_version": 3, "name": "Pomodoro Timer", "version": "1.0", "description": "Assists you to focus and get things done", "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }, "action": { "default_icon": "icon.png", "default_title": "Pomodoro Timer", "default_popup": "popup/popup.html" }, "permissions": ["storage", "alarms", "notifications"], "background": { "service_worker": "background.js" }, "options_page": "options/options.html" }
我们将 options.html
文件放在一个文件夹中,以便为项目添加结构。
创建一个 options
文件夹,并在其中添加 options.html
和 options.css
文件。
📦 Chrome-Extension-Series ┣ 🎨 icon.png ┣ 📄 manifest.json ┣ 📂 popup ┣ 📂 options ┃ ┣ 📄 options.css ┃ ┣ 📄 options.html
在 HTML 中,我们有一个最小值为 1
(1 分钟)、最大值为 60
(60 分钟)的数字 input
输入框。value
属性包含 25
(25 分钟)的默认计时器值。Save options
按钮可以让我们保存选项。
在 options.js
中
在前面的 option.js
代码中:
- 我们正在验证作为选项传入的值:我们正在验证作为选项传入的值。它不应小于
1
或大于60
。 - 我们存储新选项,重置计时器,并在计时器设置更改时将
isRunning
参数设置为false
。
在弹出窗口中显示保存的选项
现在让我们通过后台脚本读取已保存的选项,并将其显示在弹出页面上。
在 background.js
中
// create an alarm to notify user when time is up chrome.alarms.create("pomodoroTimer", { periodInMinutes: 1 / 60 }) // alarm listener chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "pomodoroTimer") { chrome.storage.local.get(["timer", "isRunning", "timeOption"], (res) => { if (res.isRunning) { let timer = res.timer + 1 let isRunning = true // console.log(timer) if(timer === 60 * res.timeOption) { this.registration.showNotification('Pomodoro Timer', { - body: "25 minutes has passed", + body: `${res.timeOption} minutes has passed!`, icon: "icon.png" }) timer = 0 isRunning = false } chrome.storage.local.set({ timer, isRunning, }) } }) } }) // storage to set and track timer variables chrome.storage.local.get(["timer", "isRunning", "timeOption"], (res) => { chrome.storage.local.set({ timer: "timer" in res ? res.timer : 0, timeOption: "timeOption" in res ? res.timeOption : 25, isRunning: "isRunning" in res ? res.isRunning : false, }) })
在 popup.js
中
// array to store tasks let tasks = []; const time = document.getElementById("time"); // Update time every 1sec function updateTime() { chrome.storage.local.get(["timer", "timeOption"], (res) => { const time = document.getElementById("time") // get no. of minutes & secs const minutes = `${ res.timeOption - Math.ceil(res.timer / 60)}`.padStart(2, "0"); let seconds = "00"; if (res.timer % 60 != 0) { seconds = `${60 -res.timer % 60}`.padStart(2, "0"); } // show minutes & secs on UI time.textContent = `${minutes}:${seconds}` }) }
如果通过设置选项来测试扩展,就会在 popup
页面上看到新值。
样式
最后,让我们来设计扩展的样式。复制并粘贴下面的代码。
在 popup.css
中
body { height: 400px; width: 350px; background: hsla(238, 100%, 71%, 1); background: linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ); background: -moz-linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ); background: -webkit-linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ); filter: progid: DXImageTransform.Microsoft.gradient( startColorstr="#696EFF", endColorstr="#F8ACFF", GradientType=1 ); } .header { display: flex; justify-content: center; height: 40px; background-color: whitesmoke; margin: -8px; padding: 5px; } #time { text-align: center; font-size: 50px; margin: 10px; font-weight: normal; color: whitesmoke; } #btn-container { display: flex; justify-content: space-evenly; } #btn-container > button { color: black; background-color: whitesmoke; border: none; outline: none; border-radius: 5px; padding: 8px; font-weight: bold; width: 100px; cursor: pointer; } #task-container { display: flex; flex-direction: column; align-items: center; padding: 20px; } .task-input { outline: none; border: none; border-radius: 4px; margin: 5px; padding: 5px 10px 5px 10px; width: 250px; } .task-delete { outline: none; border: none; height: 25px; width: 25px; border-radius: 4px; color: indianred; cursor: pointer; font-weight: 700; }
在 options.css
中
body { background: hsla(238, 100%, 71%, 1) no-repeat; background: linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ) no-repeat; background: -moz-linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ) no-repeat; background: -webkit-linear-gradient( 90deg, hsla(238, 100%, 71%, 1) 0%, hsla(295, 100%, 84%, 1) 100% ) no-repeat; filter: progid: DXImageTransform.Microsoft.gradient( startColorstr="#696EFF", endColorstr="#F8ACFF", GradientType=1 ); } h1 { color: whitesmoke; text-align: center; font-size: 50px; margin: 10px; font-weight: normal; } h2 { font-weight: normal; color: whitesmoke; } #time-option { outline: none; border: none; width: 300px; border-radius: 4px; padding: 10px; } #save-btn { display: block; margin-top: 40px; border: none; outline: none; border-radius: 4px; padding: 10px; color: black; font-weight: bold; cursor: pointer; }
小结
Chrome 浏览器扩展新手入门系列到此结束。希望它能帮助你了解构建 Chrome 扩展的基本概念。我鼓励你尝试构建自己的项目,甚至在此项目的基础上添加更多功能。祝你学习顺利。
原文地址:https://www.wbolt.com/chrome-extensions-for-beginners-part-2-practice.html