首页
/ Web-Dev-For-Beginners Carbon Trigger 浏览器扩展:基于 CO2 Signal API 的完整实现与部署

Web-Dev-For-Beginners Carbon Trigger 浏览器扩展:基于 CO2 Signal API 的完整实现与部署

2026-09-06 17:02:24作者:凤尚柏Louis

本文围绕 Web-Dev-For-Beginners 课程第 5 模块(浏览器扩展项目)的"完成版代码"文档展开,介绍 Carbon Trigger 浏览器扩展的用途、构建与安装流程,并结合仓库中 solution/src/index.js 的真实源码,深入讲解数据获取、碳强度到颜色的映射、本地存储持久化以及跨脚本消息传递的完整实现链路。读完后你将能够独立构建、加载并理解一个可运行的 CO2 强度提醒扩展。

Carbon Trigger 扩展运行截图:扩展弹窗显示区域碳强度与化石燃料占比

项目背景:把区域碳强度提醒装进浏览器

Carbon Trigger 是一个浏览器扩展:它调用 CO2 Signal API 跟踪电力消耗,在浏览器里直接提示"你所在区域的用电碳强度有多高"。文档给出的典型使用方式是"随手查一下"(ad hoc)——在需要时打开扩展,根据当前区域电网的碳强度情况,判断此时是否适合执行高耗电活动(例如文档提到,可以据此决定是否推迟开烘干机)。

扩展最直观的反馈是工具栏里的一个"彩色圆点":在扩展界面输入 API 密钥和区域代码后,圆点颜色会随区域能源使用情况变化,提示哪些高能耗活动当前"合适"、哪些应该延后。这个"圆点"指示器的概念,来源于面向加州排放数据的 Energy Lollipop 扩展(该出处记录在 5-browser-extension/README.md 的 Credits 部分)。

该扩展基于 Chromium 扩展 API 编写,因此可同时运行于 Edge、Chrome 等 Chromium 系浏览器(课程模块 5-browser-extension/README.md 说明它可在 Edge、Chrome 与 Firefox 上构建)。

环境要求与构建步骤

原始文档只要求"安装 npm 后执行两条命令",仓库中 solution/package.json 则给出了更精确的适用前提,可据此确认运行环境:

配置项 取值 说明
node >=18.0.0 engines 字段声明的最低 Node 版本
npm >=9.0.0 engines 字段声明的最低 npm 版本
webpack ^5.105.4(devDependency) 用于打包扩展产物
webpack-cli ^5.1.4(devDependency) 提供 webpack 命令行入口
axios ^1.15.0(dependency) 扩展运行时用于请求 CO2 Signal API

构建流程按 solution/README.md(以及其西语版本 solution/translation/README.es.md)描述如下:

  1. 将该目录下的代码下载一份到本地文件夹;
  2. 安装全部依赖:
npm install
  1. 使用 webpack 构建扩展:
npm run build

package.jsonscripts 字段可以看到,build 对应的就是 webpack,另外还有一个 watch 脚本(webpack --watch),可在开发期间监听源码变更并重新打包。构建完成后,产物输出到 dist 目录,浏览器加载的就是这个目录。

在 Edge 中加载未打包扩展

扩展产物并不经过应用商店分发,而是以"未打包扩展"(Load Unpacked)方式加载。按文档步骤:

  1. 点击 Edge 右上角的"三个点"菜单,进入扩展功能(Extensions)面板;
  2. 选择"Load Unpacked"(加载已解压缩的扩展);
  3. 在弹出的目录选择框中打开构建生成的 dist 文件夹,扩展即被加载。

在 Edge 中加载未打包扩展的界面截图

一个实用提示(出自模块第 3 课 3-background-tasks-and-performance/README.md):如果修改了代码,需要重新执行 npm run build,并回到扩展面板手动重新加载扩展,改动才会生效。

使用配置:API 密钥与区域代码

扩展首次打开会显示一个表单,需要填入两项配置:

  • CO2 Signal API 密钥:文档说明需通过 CO2 Signal 官方页面申请(在页面表单中填写邮箱获取),它会被写入请求头 auth-token 发送给 API;
  • 区域代码:需对应 Electricity Map 的区域划分,可从其 zones 接口查询全部可用代码。文档给出的示例是:作者位于波士顿,使用 'US-NEISO'

配置提交后,扩展会立刻发起一次数据请求并展示结果;两项配置会写入 localStorage,之后打开扩展无需重复填写。若所选区域没有可用数据,界面会显示"Sorry, data unavailable for the selected region."的提示(见下文错误处理小节)。

源码解析:主脚本的核心逻辑

仓库中 solution/src/index.js 是完成版扩展的主脚本(约 129 行),它承担表单处理、API 调用、颜色映射与状态恢复四件事。下面按数据流顺序拆解。

数据获取与校验:displayCarbonUsage

核心请求逻辑在 src/index.js#L34-L68。它使用 axios 发起 GET 请求:

await axios
  .get('https://api.co2signal.com/v1/latest', {
    params: { countryCode: region },
    headers: { 'auth-token': apiKey },
  })

即调用 CO2 Signal 的 v1/latest 接口,以 countryCode 查询参数传入区域代码,以 auth-token 请求头携带 API 密钥。请求成功后,代码先做防御性校验(L44-L47):carbonIntensity(碳强度,克 CO2/千瓦时)与 fossilFuelPercentage(化石燃料发电占比)任一为 null 就抛出异常,避免把残缺数据渲染到界面上。

校验通过后,界面显示两项关键指标(L55-L59):

  • 碳强度:取整后展示,例如 "412 grams (grams C02 emitted per kilowatt hour)";
  • 化石燃料占比:保留两位小数,例如 "63.45% (percentage of fossil fuels used to generate electricity)"。

请求失败时走 catch 分支(L62-L67):隐藏加载态与结果区,在 .errors 元素中写入"所选区域数据不可用"的提示,并输出 console.warn 便于调试。整个流程通过显式切换 loading / form / results 三个容器的 display 来管理 UI 状态。

碳强度到颜色的映射:calculateColor

calculateColorsrc/index.js#L17-L32)把数值翻译成直观颜色,相当于一个"红绿灯"系统:

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 } });
};

分档规则(克 CO2/千瓦时)与颜色对应关系如下:

| 碳强度区间 | 对应档位值 | 颜色 | 含义 | | --- | --- | --- | | 0–150 | 0 / 150 | #2AA364(绿) | 清洁电力占比高 | | 150–600 | 600 | #F5EB4D(黄) | 中等 | | 600–750 | 750 | #9E4229(橙棕) | 偏高 | | 750–800 | 800 | #381D02(深棕) | 非常高 | | ≥800 | 800 | #381D02(深棕) | 非常高 |

实现上有两个值得注意的细节。其一是求"最近档位"的技巧:对 co2Scale 按"与目标值距离差"排序后取首元素(L21-L23),再配合 findIndex 找到该档位之后第一个更大的刻度作为 scaleIndex,从而从 colors 数组中取色。其二是取色后不做任何本地渲染,而是直接通过 chrome.runtime.sendMessage{ action: 'updateIcon', value: { color } } 消息发给后台脚本(L31)——因为只有后台脚本有能力修改浏览器工具栏图标。

用户配置持久化与初始化

setUpUserL72-L80)在表单提交时把 apiKeyregion 写入 localStorage,重置错误提示、显示加载态与"清除"按钮,并立即触发一次 displayCarbonUsage

initL89-L116)在脚本启动时执行两次检查:先从 localStorage 读取已保存的 apiKeyregion,然后按两种分支处理——

  • 任一配置缺失:显示表单、隐藏结果区,等待用户填写(L102-L108);
  • 配置齐全:隐藏表单,直接用保存的配置重新拉取数据(L109-L115),实现"记住我"体验。

init 还会发送一条默认消息,把图标先置为绿色(L95-L100),让用户在数据返回前就能看到扩展已在工作。

最后,脚本末尾(L125-L129)绑定 formsubmitclearBtnclick 监听器并调用 init() 启动应用。reset 函数(L118-L123)只清除 region 一项再重新 init(),把扩展还原到待配置状态。

彩色圆点:消息传递与后台图标渲染

主脚本只负责"算出颜色",真正把工具栏图标染色的工作在后台脚本中完成。按第 3 课 3-background-tasks-and-performance/README.md 的讲解,dist/background.js 中的监听器形如:

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
  if (msg.action === 'updateIcon') {
    chrome.action.setIcon({ imageData: drawIcon(msg.value) });
  }
});

// 用 OffscreenCanvas 绘制动态图标
function drawIcon(value) {
  const canvas = new OffscreenCanvas(200, 200);
  const context = canvas.getContext('2d');
  context.beginPath();
  context.fillStyle = value.color;
  context.arc(100, 100, 50, 0, 2 * Math.PI);
  context.fill();
  return context.getImageData(50, 50, 100, 100);
}

完整链路为:主脚本取到 API 数据后调用 calculateColor(CO2)chrome.runtime.sendMessage 发送颜色 → 后台脚本的 onMessage 监听器收到消息 → 用 OffscreenCanvas 在内存中画一个填充该颜色的圆形并取回 ImageDatachrome.action.setIcon 更新工具栏图标。选择 OffscreenCanvas 而非普通 Canvas,是为了不在 UI 线程上做绘制、避免阻塞交互(这一点在课程文档的"Performance Considerations"中被明确点出)。

chrome.runtime API 在其中扮演扩展的"神经系统"角色:它负责不同脚本上下文之间的消息传递、后台任务与生命周期事件,这正是"内容脚本算数据、后台脚本改图标"这一职责分离得以成立的基础。需要说明的是,仓库 solution 目录内提供的是主脚本 src/index.js 与构建配置,manifest、弹窗页面等扩展骨架部分未在仓库中完整给出,后台脚本的完整写法以第 3 课文档中的示例为准(从源码结构看,index.js 中所有 chrome.runtime 调用都假设后台侧存在对应的 onMessage 处理者)。

延伸学习与相关路径

综合来看,Carbon Trigger 虽然代码量不大,却完整覆盖了 Chromium 扩展开发的关键面:外部 API 鉴权与异步请求、UI 状态管理、localStorage 持久化、跨脚本消息传递以及 OffscreenCanvas 动态图标绘制。按文档给出的 npm installnpm run build → Load Unpacked 三步走,再结合对 solution/src/index.js 的上述解读,就可以完整复现并改造这个碳强度提醒扩展。

登录后查看全文
热门项目推荐
相关项目推荐