Chrome 实现一个侧边栏其实很方便,因为侧边栏的框架 Chrome 已经帮我们实现好了,只需要配置权限,然后实现页面即可。

配置权限

manifest.json 文件中增加如下配置即可。permissions 添加 “sidePanel",side_panel 设置默认的文件路径 "side-panel.html"

{
    ...
    "permissions": [
        ...
        "sidePanel"
    ],
    "background": {
        "service_worker": "background.js"
    },
    "side_panel": {
        "default_path": "side-panel.html"
    }
}

当然 Chrome 也支持多个侧边栏,具体实现需求可以查阅官方文档。Chrome 支持侧边栏支持如下特性:字典侧边栏,全局侧边栏,多个侧边栏,打开侧边栏,针对特定网站的侧边栏。

side-panel.html 编码

侧边栏的代码可以很简单,然后通过 js 动态生成 DOM 即可

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>我的侧边栏</title>
  </head>
  <body>
    <div class="projects">

    </div>
    <script src="side-panel.js"></script>
  </body>
</html>

比如使用 document.querySelector('.projects').innerHTML = html; 直接在 DOM 元素中插入 HTMl 代码。或者 createElement DOM 元素,然后在其它元素后面 appendChild 等等。

点击插件图标按钮,直接打开侧边栏

// background.js
chrome.sidePanel
  .setPanelBehavior({ openPanelOnActionClick: true })
  .catch((error) => console.error(error));

跨域问题

Chrome 插件基本上不需要考虑跨域的问题。只需要在 manifest.json 文件中配置 host_permissions 权限即可。

{
  ...
  "host_permissions": [
    "https://api.example.com/*",
    "https://another-api.example.com/*"
  ],
  ...
}

当前页面的脚本可以直接发起跨域请求。如果需要请求其它域名,则需要在 background.js 中使用 chrome.runtime.onMessage 监听消息,然后在回调函数中发起请求,最后通过 sendResponse 返回结果。

// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'fetchData') {
    const init = {
      method,
      headers,
      credentials: 'include', // include cookies
    };
    fetch(request.url, init)
      .then((response) => response.json())
      .then((data) => sendResponse({ data }))
      .catch((error) => sendResponse({ error: error.message }));
    return true; // 表示异步响应
  }
});

实现一个测试侧边栏,获取百度的标题,展示在侧边栏中

manifest.json 配置

{
  "manifest_version": 3,
  "name": "测试侧边栏",
  "version": "1.0",
  "description": "一个简单的测试侧边栏插件",
  "host_permissions": [
    "https://*.baidu.com/*"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "sidePanel"
  ],
  "side_panel": {
    "default_path": "side-panel.html"
  }
}

side-panel.html

<!DOCTYPE html>
<html lang="en"></html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>我的侧边栏</title>
</head>
<body>
  <div class="projects">

  </div>
  <script src="side-panel.js"></script>
</body>
</html>

side-panel.js

// Example usage: Fetch Baidu title when the sidebar is loaded
document.addEventListener('DOMContentLoaded', () => {
  chrome.runtime.sendMessage({ type: 'FETCH', url: 'https://www.baidu.com' }, (response) => {
    if (response?.ok && response.body) {
      const titleMatch = response.body.match(/<title>(.*?)<\/title>/);
      const title = titleMatch ? titleMatch[1] : '未找到标题';
      document.querySelector('.projects').innerHTML = `<h1>${title}</h1>`;
    } else {
      console.error('Error fetching Baidu title:', response?.error);
      document.querySelector('.projects').innerHTML = `<h1>加载标题失败</h1>`;
    }
  });
});

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (!message || !message.type) return;

  if (message.type === 'PING') {
    sendResponse({ ok: true, ts: Date.now() });
  }

  // Handle cross-origin fetch with cookies from background
  if (message?.type === 'FETCH') {
    const { url, method = 'GET', headers = {}, body } = message;
    if (!url) {
      sendResponse({ ok: false, error: 'Missing url' });
      return;
    }

    const init = {
      method,
      headers,
      credentials: 'include', // include cookies
    };
    if (body !== undefined && body !== null) {
      init.body = typeof body === 'string' ? body : JSON.stringify(body);
      if (!init.headers || !('Content-Type' in init.headers)) {
        init.headers = { ...(init.headers || {}), 'Content-Type': 'application/json' };
      }
    }

    fetch(url, init)
      .then(async (res) => {
        const contentType = res.headers.get('content-type') || '';
        const text = await res.text();
        sendResponse({
          ok: res.ok,
          status: res.status,
          statusText: res.statusText,
          body: text,
          contentType,
        });
      })
      .catch((err) => {
        sendResponse({ ok: false, error: err.message || String(err) });
      });

    return true; // async
  }
});

// Optional: Open side panel on action icon click
chrome.sidePanel
  .setPanelBehavior({ openPanelOnActionClick: true })
  .catch((error) => console.error(error));

推荐阅读: