Carbon Trigger 浏览器扩展完全指南:基于 CO2 Signal API 构建碳排放追踪扩展
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
本篇导读:Carbon Trigger 是 Web-Dev-For-Beginners 课程中的实战项目,它基于 tmrow 的 CO2 Signal API,将区域电力碳排放数据实时呈现在浏览器扩展图标上。读完本文,你将掌握完整的扩展开发流程:Webpack 打包构建、Edge/Chrome 扩展加载、API 密钥与区域代码配置、localStorage 持久化,以及基于碳强度映射图标颜色的核心算法实现。本文以 翻译版完成代码文档 为骨架,结合仓库内的 完整源码 逐行剖析,保证每一步都可复现、可运行。
Carbon Trigger 扩展:在浏览器工具栏中以彩色圆点实时反映区域电力碳排放强度
项目背景与核心思路
Carbon Trigger 是一款微型网站式浏览器扩展,它的任务是回答一个简单却重要的问题:"我所在区域的电力碳排放现在有多高?"
扩展在安装后,会定期/按需查询 CO2 Signal API,获取指定区域的碳强度(carbon intensity,单位 gCO₂/kWh)和化石燃料发电占比(fossil fuel percentage),并通过浏览器扩展栏中一个彩色圆点直观地提示用户当前是否适合执行高耗电活动(例如洗衣服、烘干衣物等)。该"圆点"视觉方案受 Energy Lollipop 扩展(针对加州排放)启发,在仓库的 模块导读 中有明确说明。
整个浏览器扩展模块共三课,与本完成代码对应:
| 课程 | 主题 | 对应文件 |
|---|---|---|
| 第 1 课 | 浏览器工作原理与扩展安装 | 1-about-browsers/README.md |
| 第 2 课 | 表单、localStorage 与 API 集成 | 2-forms-browsers-local-storage/README.md |
| 第 3 课 | 后台任务、动态图标与性能 | 3-background-tasks-and-performance/README.md |
快速开始:安装、构建与加载
关联文档给出了三步上手流程,以下结合 package.json 给出完整说明。
1. 环境要求与依赖安装
前置条件:系统已安装 npm(Node.js 包管理器)。仓库的 package.json 声明了运行环境门槛:
"engines": { "npm": ">=9.0.0", "node": ">=18.0.0" }将 solution 目录 的代码下载到本地文件夹后,安装依赖:
npm install该命令会安装两类包(见 package.json):
- 开发依赖(devDependencies):
webpack ^5.105.4、webpack-cli ^5.1.4,用于模块打包; - 运行依赖(dependencies):
axios ^1.15.0,用于发起 CO2 Signal API 请求。
2. Webpack 构建
npm run buildbuild脚本映射到webpack命令,将 src/index.js 打包为dist目录下的浏览器可加载文件。除此之外 package.json 还提供了开发调试脚本:
"scripts": { "watch": "webpack --watch", "build": "webpack" }npm run watch:监听源码变更并自动重新打包,适合开发期反复调试;npm run build:一次性构建,用于加载和分发。
3. 在 Edge(及 Chromium 内核浏览器)中加载
扩展开发期通过"加载解压缩的扩展"方式安装:
- 点击浏览器右上角**"三个点"菜单**,进入"扩展"面板;
- 打开**"开发人员模式"**开关;
- 选择"加载解压缩的扩展"(Load Unpacked);
- 在弹出的文件选择器中打开
dist文件夹,扩展即被加载。
在 Edge 的扩展管理页面通过"加载解压缩的扩展"导入 dist 目录
提示:由于新版 Edge 基于 Chromium 内核,扩展 API 与 Chrome 兼容,因此源码中使用的
chrome.runtime.*系列 API 在 Edge 中同样有效(这一点在 3-background-tasks-and-performance/README.md 中有明确说明)。Firefox 亦可用于该扩展的开发测试。
4. 获取两个必要配置
扩展真正工作前,需要用户提供两项输入:
| 配置项 | 获取方式 | 示例 |
|---|---|---|
| CO2 Signal API Key | 在 co2signal.com 页面输入邮箱免费申请 | 形如xxxxx-xxxxx的令牌 |
| 区域代码(Region Code) | 依据 Electricity Map 地图对照区域代码表查询 | 波士顿地区使用US-NEISO |
区域代码与 Electricity Map 上的电网分区一一对应,中国大陆地区可在地图上查询对应的电网分区编码。填写完成后,扩展栏中的彩色圆点会随区域碳排放强度实时变色。
源码级剖析:扩展是如何工作的
关联文档描述了"输入 API Key 和区域后圆点变色"的最终效果,其背后的完整实现位于 solution/src/index.js。下面按数据流顺序逐段拆解。
DOM 引用捕获
扩展启动时首先通过document.querySelector()捕获所有需要操作的界面元素,分为表单字段与结果展示两组:
// form fields const form = document.querySelector('.form-data'); const region = document.querySelector('.region-name'); const apiKey = document.querySelector('.api-key'); // results const errors = document.querySelector('.errors'); const loading = document.querySelector('.loading'); const results = document.querySelector('.result-container'); const usage = document.querySelector('.carbon-usage'); const fossilfuel = document.querySelector('.fossil-fuel'); const myregion = document.querySelector('.my-region'); const clearBtn = document.querySelector('.clear-btn');这些 CSS 类名与 第 1 课文档 中定义的 HTML 结构一一对应:.region-name和.api-key是配置表单的输入框,.carbon-usage、.fossil-fuel、.my-region是结果区域的数据展示位,.loading与.errors负责加载中和报错状态,.clear-btn是"更改区域"按钮。
表单提交与用户配置持久化
扩展在页面底部完成事件绑定并立即初始化:
form.addEventListener('submit', (e) => handleSubmit(e)); clearBtn.addEventListener('click', (e) => reset(e)); //start app init();handleSubmit首先调用e.preventDefault()阻止浏览器默认的表单刷新行为,随后把输入值交给setUpUser:
const handleSubmit = async (e) => { e.preventDefault(); setUpUser(apiKey.value, region.value); }; const setUpUser = async (apiKey, region) => { localStorage.setItem('apiKey', apiKey); localStorage.setItem('region', region); loading.style.display = 'block'; errors.textContent = ''; clearBtn.style.display = 'block'; //make initial call displayCarbonUsage(apiKey, region); };关键设计点:
- localStorage 持久化:API Key 与区域代码以键值对形式写入浏览器本地存储,下次打开扩展时无需重新输入;
- UI 状态切换:保存后立即显示加载指示器、清空历史错误、展示"清除"按钮;
- 立即发起首次请求:保存配置后马上调用
displayCarbonUsage拉取数据。
初始化与重置:记忆用户身份
init()是扩展的状态分派中心,负责判断"老用户还是新用户":
const init = async () => { const storedApiKey = localStorage.getItem('apiKey'); const storedRegion = localStorage.getItem('region'); //set icon to be generic green chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: 'green' }, }); if (storedApiKey === null || storedRegion === null) { // 新用户:展示配置表单 form.style.display = 'block'; results.style.display = 'none'; loading.style.display = 'none'; clearBtn.style.display = 'none'; errors.textContent = ''; } else { // 老用户:直接加载已保存的数据 results.style.display = 'none'; form.style.display = 'none'; displayCarbonUsage(storedApiKey, storedRegion); clearBtn.style.display = 'block'; } };reset()则让用户更换区域:
const reset = async (e) => { e.preventDefault(); //clear local storage for region only localStorage.removeItem('region'); init(); };注意这里只清除region而保留apiKey——API Key 无需重复输入,设计细节上保持了较好的用户体验。浏览器扩展的 localStorage 与普通网页隔离,可通过 DevTools 的 Application 面板查看(详见 第 2 课文档)。
API 调用:CO2 Signal 数据获取
displayCarbonUsage是整个扩展的数据引擎,使用 axios 向 CO2 Signal API 发起 GET 请求:
const displayCarbonUsage = async (apiKey, region) => { try { await axios .get('https://api.co2signal.com/v1/latest', { params: { countryCode: region }, headers: { 'auth-token': apiKey }, }) .then((response) => { const data = response?.data?.data; // ✅ Validate required data before using if (data?.carbonIntensity == null || data?.fossilFuelPercentage == null) { throw new Error('Missing carbon intensity or fossil fuel data'); } let CO2 = Math.floor(data.carbonIntensity); calculateColor(CO2); loading.style.display = 'none'; form.style.display = 'none'; myregion.textContent = region; usage.textContent = Math.round(data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)'; fossilfuel.textContent = data.fossilFuelPercentage.toFixed(2) + '% (percentage of fossil fuels used to generate electricity)'; results.style.display = 'block'; }); } catch (error) { console.warn('Data fetch failed:', error.message); loading.style.display = 'none'; results.style.display = 'none'; errors.textContent = 'Sorry, data unavailable for the selected region.'; } };请求细节拆解:
| 要素 | 值 | 说明 |
|---|---|---|
| 端点 | https://api.co2signal.com/v1/latest | CO2 Signal 的实时数据接口 |
| 认证 | Headerauth-token: <apiKey> | 通过请求头携带 API Key,而非 URL 参数 |
| 查询参数 | countryCode: <region> | 传入区域代码,如US-NEISO |
| 返回数据 | data.carbonIntensity | 碳强度(gCO₂/kWh),用于圆点配色 |
| 返回数据 | data.fossilFuelPercentage | 化石燃料发电占比(%),展示给用户 |
健壮性处理(可对照 第 2 课文档 的 fetch 版本理解演进):
- 可选链 + 数据校验:
response?.data?.data避免空引用崩溃;carbonIntensity或fossilFuelPercentage缺失时主动抛出异常; - try/catch 兜底:网络错误或数据不可用时,隐藏 loading 与结果区,在
.errors中展示友好提示 "Sorry, data unavailable for the selected region."; - UI 联动:数据就绪后隐藏表单、填入区域名、碳强度(克/千瓦时)与化石燃料占比(保留两位小数),再显示结果容器。
颜色计算算法:数据 → 圆点颜色
这是本扩展最有特色的部分,将数值碳强度映射为五种视觉颜色。注意:源码头部的calculateColor是隐式全局函数(未用const声明),这是为了兼容后续课程逐步添加代码的教学节奏,其算法逻辑与 第 3 课文档 完全一致:
calculateColor = async (value) => { let co2Scale = [0, 150, 600, 750, 800]; let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02']; let closestNum = co2Scale.sort((a, b) => { return Math.abs(a - value) - Math.abs(b - value); })[0]; let num = (element) => element > closestNum; let scaleIndex = co2Scale.findIndex(num); let closestColor = colors[scaleIndex]; chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } }); };算法执行流程:
- 定义色阶:
co2Scale = [0, 150, 600, 750, 800]表示碳强度阈值(gCO₂/kWh),colors数组与之对应——绿色(清洁)、黄色(中等)、橙色(偏高)、深棕(很高); - 寻找最近阈值:通过对数组按"与输入值的绝对差"排序取第一项,得到最近的档位;
- 定位色标索引:用
findIndex找出第一个大于最近阈值的元素下标,映射到对应颜色; - 通知后台更新图标:通过
chrome.runtime.sendMessage({ action: 'updateIcon', ... })将颜色发给扩展后台脚本,后台再调用chrome.action.setIcon用 OffscreenCanvas 绘制圆形图标(此部分详见 3-background-tasks-and-performance/README.md 的drawIcon实现)。
注意:
co2Scale.sort()会原地修改数组。虽然示例代码中排序后立即使用不影响本次结果,但生产代码应使用[...co2Scale].sort()拷贝排序,避免污染原始色阶数组。
数据流全景:从表单到图标
综合以上实现,Carbon Trigger 的完整调用链为:
用户输入 API Key + 区域代码 │ 提交表单 (submit 事件) ▼ handleSubmit() ──► setUpUser() │ ├─ localStorage.setItem('apiKey'/'region') 持久化 │ └─ displayCarbonUsage() 首次拉取 ▼ displayCarbonUsage() ──► axios GET api.co2signal.com/v1/latest │ ├─ header: auth-token │ └─ params: countryCode ▼ 响应数据校验 ──► calculateColor(carbonIntensity) │ └─ chrome.runtime.sendMessage({action:'updateIcon'}) ▼ 后台脚本 chrome.action.setIcon ──► 工具栏彩色圆点更新扩展启动时init()会先读取 localStorage:若存在历史配置,直接进入数据展示路径;否则展示配置表单。这个"记忆-恢复"闭环正是第 2 课 LocalStorage 与第 3 课后台消息机制的实战融合。
常见问题排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 提示 "data unavailable for the selected region" | API Key 无效、区域代码格式错误或网络异常 | 核对auth-token与countryCode取值,检查控制台console.warn输出 |
| 扩展图标不出现/不变色 | 后台脚本未监听updateIcon消息 | 确认dist中后台脚本包含chrome.runtime.onMessage监听与drawIcon函数 |
| 修改源码后无变化 | 未重新构建 | 运行npm run build,并在扩展管理页点击"重新加载" |
| 表单反复出现 | localStorage 被清除 | 重新输入 API Key 与区域代码,或检查浏览器是否清理了扩展存储 |
深入阅读
- 完整实现源码:solution/src/index.js
- 依赖与构建脚本:solution/package.json
- 第 1 课(浏览器基础与扩展安装):1-about-browsers/README.md
- 第 2 课(表单、localStorage 与 API):2-forms-browsers-local-storage/README.md
- 第 3 课(后台任务、动态图标与性能剖析):3-background-tasks-and-performance/README.md
- 入门脚手架代码(对照学习):start/README.md
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考