首页
/ Carbon Trigger 浏览器扩展完整实现解析:基于 CO2 Signal API 的电力碳排放强度提醒工具

Carbon Trigger 浏览器扩展完整实现解析:基于 CO2 Signal API 的电力碳排放强度提醒工具

2026-09-07 22:23:01作者:凌朦慧Richard

本篇技术指南以 Web-Dev-For-Beginners 仓库第 5 课(Browser Extension)的完整解答代码为对象,围绕 5-browser-extension/solution/README.md 所描述的 Carbon Trigger 扩展,从环境准备、依赖安装、Webpack 构建、Edge 加载,到源码逐段解读的完整链路展开讲解。读者读完可以独立把该扩展从零构建并安装到自己的浏览器中,同时理解 Manifest V3 扩展中“前端弹窗 + localStorage 状态 + Service Worker 后台绘制图标 + 第三方 API 请求”这一套典型协作模式。

Carbon Trigger 扩展运行效果截图

一、扩展要做什么:让浏览器的“小圆点”提示你当前区域用电强度

Carbon Trigger 是一个浏览器扩展示例,它的核心创意是:利用 tmrow 提供的 CO2 Signal API 追踪电力使用情况,在浏览器扩展栏常驻一个彩色圆点,让你在浏览网页时随时感知所在区域发电产生的碳强度(carbon intensity),从而帮助用户对“要不要现在跑视频转码、要不要开启大型下载”这类高耗能活动做出更环保的判断。

该“彩色圆点”交互理念源自面向加州排放场景的 Energy Lollipop 扩展。示例中默认的经典场景是位于波士顿(区域码 US-NEISO)的用户,但任何在 Electricity Map 覆盖区域 内的地区理论上都可以使用对应区域码。整个示例遵循课程“三节课完成一个扩展”的脉络:浏览器工作原理、表单与本地存储、后台任务与性能,最后在本目录汇聚为完整可运行实现。

二、前置环境要求:Node 与 npm

在开始之前,需要确认本机已经安装了 npm。仓库在 5-browser-extension/solution/package.jsonengines 字段明确声明了版本下限:

"engines": {
  "npm": ">=9.0.0",
  "node": ">=18.0.0"
}

也就是说,建议使用 Node.js 18.0.0 及以上、npm 9.0.0 及以上的运行环境,否则可能出现 Webpack 构建或依赖安装的兼容性问题。可用以下命令核对当前环境:

node -v
npm -v

除了运行环境外,由于本示例是练习性质、涉及调用外部 API 服务,还需要两个账号层面的准备:一个 CO2 Signal API Key(用于鉴权请求碳强度数据),以及对应你所在地区的电力区域码(用于定位数据),这两项的具体获取方式见下文“六、获取 API Key 与配置区域码”。

三、获取代码、安装依赖并执行构建

在本地任意文件夹放置一份本课程代码后,按以下顺序执行:

1. 安装全部依赖

npm install

依据 package.json 的声明,该命令会安装两类包:

  • 生产依赖axios(^1.15.0),用于向 CO2 Signal API 发起 HTTP 请求;
  • 开发依赖webpack(^5.105.4)与 webpack-cli(^5.1.4),用于把 src/ 下的源码打包成浏览器可直接加载的 dist/ 产物。

2. 使用 Webpack 构建扩展

npm run build

该命令等价于直接运行 webpack,会读取 src/index.js 作为入口进行打包。开发调试阶段也可以使用增量监听模式:

npm run watch

对应 package.jsonscripts 字段定义如下:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "watch": "webpack --watch",
  "build": "webpack"
}

3. 认识 dist 构建产物

构建完成后,会在 5-browser-extension/solution/dist 下生成浏览器可直接加载的一整套扩展文件:

文件 作用
manifest.json Manifest V3 清单,声明扩展名称、版本、权限、后台与弹窗
background.js Service Worker,负责监听消息并把碳强度映射为工具栏彩色圆点图标
index.html 点击工具栏图标弹出的设置/结果页面
main.js Webpack 打包后的入口逻辑(对应 src/index.js
styles.css 弹窗样式
images/ 弹窗头部展示的插画资源

其中 dist/manifest.json 是典型的 MV3 结构:

{
  "manifest_version": 3,
  "name": "My Carbon Trigger",
  "version": "0.1.0",
  "host_permissions": ["<all_urls>"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "index.html"
  }
}

值得注意的三个关键点:

  • manifest_version: 3:采用 Manifest V3 规范,后台逻辑由 Service Worker 承载,与课程 3-background-tasks-and-performance 的内容一一对应;
  • action.default_popup:指向 index.html,即点击工具栏图标时弹出设置界面;
  • host_permissions: ["<all_urls>"]:允许扩展向任意域名发起跨域请求,这是能调用 CO2 Signal API 的前提。

四、弹窗界面与表单结构

构建前的原始 HTML 位于课程各阶段练习中,而本目录的 dist/index.html 即打包后实际加载的弹窗页面。其结构包含两块核心区域:

第一块:首次使用时的信息录入表单

<form class="form-data" autocomplete="on">
  <label for="region">Region Name</label>
  <input type="text" id="region" required class="region-name" />
  <label for="api">Your API Key from tmrow</label>
  <input type="text" id="api" required class="api-key" />
  <button class="search-btn">Submit</button>
</form>

两个输入框分别收集区域码与 CO2 Signal 的 API Key,均为必填项。该表单用于首次设置,用户只需要输入一次,后续会自动保存。

第二块:数据展示区

<div class="loading">loading...</div>
<div class="errors"></div>
<div class="result-container">
  <p><strong>Region: </strong><span class="my-region"></span></p>
  <p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
  <p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>

其中 .loading 用于请求期间的加载提示,.errors 用于展示失败信息,.result-container 负责呈现区域、碳强度、化石燃料占比三项数据;“Change region”按钮允许用户清除保存的地区并重新设置。课程 2-forms-browsers-local-storage 正是围绕这套表单与存储逻辑展开讲解的。

五、核心逻辑逐段解析:状态、请求与图标

全部交互逻辑都集中在 solution/src/index.js,下面按执行时序拆解。

1. 选择 DOM 节点与声明辅助函数

文件开头用 document.querySelector 一次性抓取表单、结果区、错误区等所需节点,随后定义了 calculateColor——把“碳强度数值”映射成工具栏图标颜色的核心函数:

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

其工作原理是把当前碳强度值与刻度数组 [0, 150, 600, 750, 800] 做最近邻匹配,得到索引后再查颜色表:

  • 碳强度越接近 0(如 #2AA364 绿色):电力相对清洁,适合进行高耗能活动;
  • 介于 150~600(如 #F5EB4D 黄色):碳排放中等;
  • 达到 750~800(如 #9E4229#381D02 深棕褐色):化石燃料占比很高,应避免高耗能操作。

最终通过 chrome.runtime.sendMessage({ action: 'updateIcon', value: { color } }) 把颜色值投递给后台 Service Worker。

2. 请求碳强度数据

displayCarbonUsage(apiKey, region) 负责实际的数据获取与界面渲染,源码中真实请求的接口为 https://api.co2signal.com/v1/latest

await axios
  .get('https://api.co2signal.com/v1/latest', {
    params: { countryCode: region },
    headers: { 'auth-token': apiKey },
  })
  .then((response) => {
    const data = response?.data?.data;

    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(...);

实现要点:

  • 区域码通过 params: { countryCode: region } 传递,API Key 放在自定义请求头 auth-token 中——这与 CO2 Signal 官方鉴权方式一致;
  • 渲染前对返回数据做了存在性校验(carbonIntensityfossilFuelPercentage 二者任一缺失即抛错),保证后续 toFixed(2)Math.round 不会对空值操作;
  • 成功后隐藏 loading 与表单、显示结果容器,并以两行文本分别输出“每千瓦时排放的克数”与“发电所用化石燃料占比”;
  • 失败时(如区域码无效、配额耗尽、网络异常)进入 catch 分支,隐藏 loading 与结果、在 .errors 中展示提示语 Sorry, data unavailable for the selected region.

3. 保存用户设置并触发首次请求

const setUpUser = async (apiKey, region) => {
  localStorage.setItem('apiKey', apiKey);
  localStorage.setItem('region', region);
  loading.style.display = 'block';
  errors.textContent = '';
  clearBtn.style.display = 'block';
  displayCarbonUsage(apiKey, region);
};

setUpUser 会把 API Key 与区域码写入 localStorage,这样用户关闭浏览器再打开后无需重复输入。它由表单的 submit 事件触发:

const handleSubmit = async (e) => {
  e.preventDefault();
  setUpUser(apiKey.value, region.value);
};

form.addEventListener('submit', (e) => handleSubmit(e));

4. 启动时的状态恢复:init

扩展每次弹出时都会执行 init(),它的职责是“根据 localStorage 是否存在历史配置,决定显示表单还是直接展示结果”:

const init = async () => {
  const storedApiKey = localStorage.getItem('apiKey');
  const storedRegion = localStorage.getItem('region');

  chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: 'green' } });

  if (storedApiKey === null || storedRegion === null) {
    form.style.display = 'block';
    results.style.display = 'none';
    ...
  } else {
    results.style.display = 'none';
    form.style.display = 'none';
    displayCarbonUsage(storedApiKey, storedRegion);
    clearBtn.style.display = 'block';
  }
};

值得一提的细节:无论是否已有配置,init 都会先把图标重置为绿色(color: 'green'),避免用户看到上一次残留的颜色状态,随后若有缓存配置则直接调用 displayCarbonUsage 拉取最新数据。

5. 更换区域的清除逻辑

const reset = async (e) => {
  e.preventDefault();
  localStorage.removeItem('region');
  init();
};

clearBtn.addEventListener('click', (e) => reset(e));

“Change region”按钮只移除 region 这一项而保留 apiKey,随后重新执行 init() 走“未配置”分支显示表单,实现只改区域、不重复填 Key 的体验。

6. 后台 Service Worker:把颜色画成小圆点

前端发来的 updateIcon 消息由 solution/dist/background.js 接收处理:

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

function drawIcon(value) {
  let canvas = new OffscreenCanvas(200, 200);
  let 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);
}

它使用 OffscreenCanvas 在内存中绘制一个指定颜色的实心圆,再通过 chrome.action.setIcon 实时替换浏览器工具栏的扩展图标——这就是“圆点随碳强度变色”的实现源头。源码注释也标明该画法思路借鉴自 Energy Lollipop 扩展。这一节与课程 3-background-tasks-and-performance 中关于 Service Worker 与后台消息通信的知识点形成闭环。

六、获取 API Key 与配置区域码

扩展要想真正工作,必须完成两项外部配置:

  1. CO2 Signal API Key:前往 CO2 Signal 官网,在首页输入邮箱即可通过邮件获取一个免费 API Key。它对应源码中请求头里的 auth-token 字段;
  2. 区域码(zone code):区域码用于告诉 CO2 Signal 你要查询哪个电力市场/电网的数据。可以在 Electricity Map 官网地图上查看你所在区域对应的代码,其 API 层也提供可用区域清单接口 http://api.electricitymap.org/v3/zones

以文档给出的波士顿为例,其区域码为 US-NEISO(新英格兰独立系统运营商)。如果你在北京、伦敦或东京等地区,需要先查证该区域是否被 CO2 Signal 数据源覆盖,并填写对应代码,否则会得到 data unavailable 的错误提示。需要注意的是:示例演示的 CO2 Signal 接口与鉴权格式以本仓库源码所写为准,若官方后续调整接口版本,需要相应更新 solution/src/index.js 中的 URL 与参数。

七、在 Edge(或其他 Chromium 内核浏览器)中加载安装

构建产物就绪后,按以下步骤把扩展加载进 Edge:

  1. 打开浏览器,点击右上角“三点”菜单,进入“扩展”面板;
  2. 打开页面左下角的**“开发人员模式”**开关;
  3. 点击**“加载解压缩的扩展”**(Load Unpacked);
  4. 在弹出的目录选择框中选中本项目的 dist 文件夹,确认后扩展即加载成功;
  5. 建议在扩展管理页点击“重新加载”图标进行刷新(本地修改源码重新 build 后同样需要刷新才能生效)。

在 Edge 中加载未打包扩展的安装步骤

由于该扩展基于 Manifest V3 开发,而 Chrome、Edge 等主流浏览器均遵循这一规范,理论上同一份 dist 产物也可通过 Chrome 的 chrome://extensions 开发者模式加载,只是本仓库文档以 Edge 作为演示环境。

八、使用效果与结果解读

安装并点击扩展图标后,在弹出的界面中输入 CO2 Signal API Key 与区域码并提交:

  • 首次提交后,数据会写入 localStorage,之后每次打开浏览器弹出扩展都会自动刷新最新数据;
  • 工具栏上的扩展图标会立即变为当前区域的碳强度对应颜色:偏绿表示电力较清洁,偏黄/棕表示化石燃料占比高;
  • 弹窗内会显示三项具体数据——区域码碳强度(每千瓦时排放的克数)化石燃料发电占比(%)

借助这个“一瞥即知”的彩色圆点,用户可以在电力较清洁的时间段安排洗衣、充电、视频渲染等高耗能活动,从而把碳中和理念落到日常使用习惯中。文档特别声明,此“圆点系统”的设计灵感来自面向加州碳排放场景的 Energy Lollipop 扩展。

九、从练习版到完整版:继续深入学习的路径

本目录(solution/)代表“完整可运行代码”,而在课程体系中还有一个起步版 start/ 目录(见 5-browser-extension/start),二者都包含 src/package.jsondist/ 骨架,适合学习者先自行编码再对照本解答。与之配套的三节理论课程分别是:

将本节源码与课程原文对照阅读,即可完整掌握“浏览器扩展 + 外部数据 API + 本地存储 + 后台图标刷新”这一条在真实 Web 开发中反复出现的工具链实现范式。需要说明的是,仓库中部分语言目录下的课程文档与图片说明由 AI 翻译生成,若个别表述存在歧义,应以英文原版文档为准。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388