Carbon Trigger 浏览器扩展完整实现解析:基于 CO2 Signal API 的碳足迹提醒工具
Carbon Trigger 是 Web-Dev-For-Beginners 开源课程第 5 模块「浏览器扩展」的收官实战项目:利用 tmrow 提供的 CO2 Signal API 实时查询某个地区的电力碳排放强度,并把结果以「彩色圆点」图标的形式直接呈现在浏览器工具栏中,提醒你在电网碳排较低时再安排洗衣机、烘干机等高耗电活动。本文以 solution 完整代码说明(即本仓库 translations/ar/5-browser-extension/solution/translation/README.fr.md 对应的阿拉伯语译本源文档)为骨架,结合 5-browser-extension/solution/src/index.js 与 5-browser-extension/solution/package.json 等源码实现,带你走通「环境准备 → 构建打包 → 浏览器加载 → API 配置 → 源码级原理」的完整链路。
扩展要解决什么问题
电力供应商无法区分同一电网下电流的来源是煤、天然气还是风能、太阳能,因此「现在用电是否环保」对普通用户是不可见的。Carbon Trigger 的思路是把这一信息可视化:在浏览器扩展栏显示一个随区域碳排放强度实时变色的圆点,让用户在看到数据后自行判断当下是否适合进行能耗较高的活动。
该扩展即用即查(ad hoc):用户只需在弹窗表单中输入 CO2 Signal 的 API Key 与地区代码,即可获得本地电力碳排放强度读数,并以此为参考安排用电活动。例如区域电网负载较高时,可以推迟使用烘干机这类高碳排活动。其「彩色圆点」图标方案借鉴了面向加州碳排放的 Energy Lollipop 扩展的设计理念,这一概念来源在 5-browser-extension/README.md 的 Credits 部分有明确说明。
在课程体系中,本扩展配套三节前置课程:浏览器工作原理与扩展部署、表单与本地存储、后台任务与性能优化。模块总体面向 Edge、Chrome、Firefox 设计,教程以 Edge 为演示环境。
环境准备与代码结构
开始前需要一台装有 npm 的电脑,并下载课程代码副本。以本仓库的 solution 目录为基准,其关键文件如下:
| 文件 | 作用 |
|---|---|
| 5-browser-extension/solution/package.json | 项目依赖与构建脚本定义 |
| 5-browser-extension/solution/src/index.js | 扩展弹窗(popup)全部业务逻辑 |
| 5-browser-extension/start/src/index.js | 初学者脚手架,仅含 6 步注释占位 |
| 5-browser-extension/solution/README.md | 完成版代码的使用说明 |
从 package.json 可见运行时约束与依赖细节:
- 运行时要求:
engines字段声明node >= 18.0.0、npm >= 9.0.0,低于该版本可能出现安装或构建异常; - 依赖(dependencies):
axios ^1.15.0,用于发起 CO2 Signal API 请求; - 开发依赖(devDependencies):
webpack ^5.105.4与webpack-cli ^5.1.4,负责把src/index.js打包输出到dist目录; - 构建脚本:
build为webpack,另有watch(webpack --watch)便于开发期增量打包; - 元信息:关键词
chrome extension/edge extension/carbon usage tracker,许可 MIT。
初学者可对照 start 脚手架 学习——它把整个扩展逻辑拆成了带编号的 6 个步骤注释(表单字段、结果区、API 调用、用户凭据设置、表单提交处理、初始化检查、事件监听与启动),自上而下理解 solution 源码会轻松很多。
安装依赖并构建扩展
在 solution 目录下依次执行两条命令即可完成构建。先安装全部依赖包:
npm install
再通过 webpack 构建扩展:
npm run build
构建完成后会在目录中生成 dist 文件夹,其中就是可被浏览器以「未打包扩展」方式加载的产物。
在浏览器中加载扩展
以 Edge 为例(该流程对同为 Chromium 内核的 Chrome 同样适用),加载步骤如下:
- 点击浏览器右上角「三点」菜单,进入「扩展」(Extensions)面板;
- 开启「开发人员模式」;
- 选择「加载解压缩的扩展」(Load Unpacked)以加载新扩展;
- 在弹出对话框中选择之前构建生成的
dist文件夹,扩展即被加载。
需要强调的是:在 Edge 中开发时使用的是 chrome.* 系列 API(详见后文),因为新版 Edge 基于 Chromium 浏览器引擎构建,可以直接复用这些工具——这一兼容性说明同样出现在 第 3 课 README 中。
获取 API Key 与地区代码
扩展只有在拿到两类凭据后才能真正工作:
- CO2 Signal API Key:前往 co2signal.com 官网,在页面输入邮箱即可通过邮件获取;
- 地区代码(Zone Code):对应 Electricity Map 地图上各区域的标识符,例如波士顿使用
US-NEISO,可在 electricitymap.org/map 对照地图查找(教程给出的查询入口为http://api.electricitymap.org/v3/zones)。
把 API Key 与地区代码输入扩展弹窗界面后,浏览器扩展栏中的彩色圆点会变为对应当前区域能耗的颜色,从而提示哪些高能耗活动适合此刻进行。
源码级解析:从表单输入到结果展示
solution 的全部弹窗逻辑集中在 5-browser-extension/solution/src/index.js,核心调用链如下。
1. DOM 元素绑定
脚本顶部一次性取得表单字段与结果显示区两类 DOM 引用(表单字段为 .form-data 下的 .region-name 与 .api-key;结果区包括 .errors、.loading、.result-container、.carbon-usage、.fossil-fuel、.my-region、.clear-btn),后续所有读写都通过这些引用完成。
2. 发起 API 请求与数据校验
displayCarbonUsage 函数 负责真正调用 CO2 Signal 接口:
axios
.get('https://api.co2signal.com/v1/latest', {
params: { countryCode: region },
headers: { 'auth-token': apiKey },
})
值得注意的请求细节:API Key 通过名为 auth-token 的请求头发送;地区代码通过 countryCode 查询参数传递;接口端点固定为 /v1/latest。收到响应后先对数据做存在性校验(data.carbonIntensity 与 data.fossilFuelPercentage 缺一即抛错),随后:
- 用
Math.floor取出碳强度整数值并交给calculateColor换算图标颜色; - 隐藏 loading 与表单,显示结果区;
- 写入三个文本项:区域代码、
carbonIntensity(保留格式「xx grams (grams C02 emitted per kilowatt hour)」)、fossilFuelPercentage.toFixed(2)(化石燃料发电占比百分比); - 任一环节失败则进入 catch 分支:隐藏加载态与结果区,错误栏显示「Sorry, data unavailable for the selected region.」提示,并用
console.warn输出具体失败原因。
3. 碳强度到颜色的映射算法
calculateColor 是整个「圆点」系统的视觉核心。代码里预置了两组等长数组:
| 碳强度刻度(gCO2/kWh) | 对应颜色 | 语义 |
|---|---|---|
| 0 | #2AA364(绿) |
清洁电网 |
| 150 | #F5EB4D(黄) |
中度 |
| 600 | #9E4229(橙棕) |
高碳排 |
| 750 | #381D02(深棕) |
很高 |
| 800 | #381D02(深棕) |
很高 |
算法分三步:先用「与输入值绝对差最小」的排序找到最接近的刻度点;再通过 co2Scale.findIndex(num) 找到第一个大于该刻度点的下标(num = (element) => element > closestNum);最后用该下标在颜色数组中取出对应颜色。若当前取到的 CO2 值位于两个刻度之间(例如介于 0 与 150 之间),会命中绿色——因此绿色并非仅在刻度 0 上出现,凡是落到「距离绿刻度最近」区间的值都会显示为绿色,实现近似分段连续的颜色反馈。
取色完成后通过消息机制通知浏览器更新图标:
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
chrome.runtime API 相当于扩展的「神经系统」:它负责扩展各脚本间的消息通信、生命周期事件管理以及 URL 相对路径到绝对路径的转换。
4. 本地存储与状态初始化
setUpUser 与 init 共同实现「记住用户」的能力:
- 表单提交后,
setUpUser把apiKey与region写入localStorage,立即显示加载动画并触发首次数据请求; - 扩展每次打开时执行
init():先发送默认绿色图标的updateIcon消息,让用户从加载起就看到「扩展在正常工作」的可视反馈;随后检查localStorage——若无已存凭据则展示表单,若有则跳过表单直接请求数据并显示清除按钮; - reset 函数 只移除
region一项(localStorage.removeItem('region'))后重新init(),这也是「清除当前地区数据」按钮的行为——注意实现上故意保留了 API Key,便于用户下次只需重选地区。
事件绑定位于文件末尾:form 的 submit 触发 handleSubmit,clearBtn 的 click 触发 reset,最后调用一次 init() 启动应用。
消息传递与后台图标绘制(背景任务原理)
从代码结构看,src/index.js 只负责「计算颜色并发出 updateIcon 消息」,真正的图标绘制发生在后台脚本中。按照 第 3 课 README 的讲解,需要为构建产物 dist 中的 background.js 注册消息监听:
// Listen for messages from the content script
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.action.setIcon({ imageData: drawIcon(msg.value) });
}
});
// Draw dynamic icon using Canvas API
// Borrowed from energy lollipop extension - nice feature!
function drawIcon(value) {
// Create an offscreen canvas for better performance
const canvas = new OffscreenCanvas(200, 200);
const context = canvas.getContext('2d');
// Draw a colored circle representing carbon intensity
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
// Return the image data for the browser icon
return context.getImageData(50, 50, 100, 100);
}
其工作流程可概括为一条消息链:calculateColor → chrome.runtime.sendMessage({action:'updateIcon', ...}) → 后台脚本 onMessage 监听 → drawIcon 用 OffscreenCanvas 绘制彩色圆形 → chrome.action.setIcon 更新工具栏图标。这里有两个值得学习的性能点:
- OffscreenCanvas 离屏绘制:不阻塞主界面线程,保证弹窗 UI 流畅,符合课程强调的「高效渲染不阻塞 UI」原则;
- 消息驱动的职责分离:数据抓取、颜色换算、图标渲染分属不同脚本上下文,通过消息传递解耦,与课程的 SPA/渲染管线(Parse HTML → DOM Tree → Render Tree → Layout → Paint → Composite)优化思想一脉相承。
关于 chrome.action.setIcon 或 chrome.browserAction 的版本差异,具体取决于扩展 manifest 中声明的 manifest version,实际使用请以构建产物所在 dist 目录内的 manifest 配置为准。若希望独立剖析扩展性能,第 3 课还建议直接在扩展自身(它本身就是独立的浏览器实例)内启动 DevTools,进入 Performance 面板录制、分析时间线与事件日志,观察是否存在超过 15ms 的长任务。
把扩展用起来与课程延伸
配置完成后,扩展栏圆点的颜色会随区域实时碳排数据变化:绿色代表当下适合运行高耗电任务,深棕则提示电网正处于高碳状态。这个「随用随查」的提醒工具完整展示了浏览器扩展开发中的几个核心技能组合:表单交互、异步 API 调用、localStorage 持久化、chrome.runtime 消息传递、Canvas 动态图标绘制,以及后台任务与性能意识。
如果你想从零练习,可对照 start 脚手架 的编号占位逐步补全逻辑;若想深入原理,可继续阅读第 1 课了解浏览器扩展架构(扩展即「为特定任务量身定制的小型网站」),第 2 课深入表单校验与本地存储,第 3 课了解渲染管线、资源优化与性能剖析工具。完成后用 npm run build 重新构建、在浏览器「扩展」页点击刷新重载扩展(不要漏掉这步),即可观察图标随全球各地真实碳数据的变化。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

