首页 / 浏览器扩展开发入门教程 / 侧边栏 side_panel

浏览器扩展开发入门教程

侧边栏 side_panel

本教程共 56 篇 · 第 30 篇 · 更新于 2026-08-13 · 约 7 分钟阅读

side_panelsidePanel侧边栏用户界面Manifest V3setPanelBehavior

本节目标:学完你能给扩展加一个停靠在浏览器右侧的侧边栏,会用清单声明默认页面,也会用 sidePanel API 控制它什么时候开、在哪个标签页里显示哪个页面。

前面讲的 popup 有个天生的毛病:一点别处就关。你在里面填了半张表,切个标签页回来,全没了。侧边栏解决的正是这件事——它是一块常驻在浏览器窗口一侧的区域,你切标签、滚页面,它都还在。

翻译工具、AI 助手、划词笔记、阅读清单,这类需要”边看网页边操作”的功能,都适合放侧边栏。它是 Manifest V3 里比较新的界面组件,用起来比 popup 稍复杂,但套路很固定。

30-1 侧边栏和 popup 怎么选

先把区别说明白,免得你选错组件写一半再推翻。

维度popup 弹出页侧边栏 side panel
生命周期失焦即关,页面销毁用户手动关闭前一直在
与网页并存不能,遮挡页面能,左右并排
适合场景快速操作、开关、看一眼状态长时间伴随浏览、持续输入
状态保持关了就丢,需要存 storage切标签页也不销毁

一句话判断:用完就走的用 popup,要陪着用户的用侧边栏。两个也可以同时有,popup 里放开关,侧边栏放正文,各管一段。

30-2 清单声明:两个东西都不能少

侧边栏要生效,清单里得写两处:sidePanel 权限和 side_panel 字段。

{
  "manifest_version": 3,
  "name": "我的侧边栏工具",
  "version": "1.0.0",
  "permissions": ["sidePanel"],
  "side_panel": {
    "default_path": "sidepanel.html"
  },
  "action": {
    "default_title": "点击打开侧边栏"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}
  • permissions 里的 "sidePanel":不写它,chrome.sidePanel 就是 undefined,后面所有 API 全报错。
  • side_panel.default_path:默认加载哪个 HTML,路径相对扩展根目录。
Note

default_path 是”全局默认页”。声明之后,用户在工具栏图标上右键,就能看到打开侧边栏的入口。不写 side_panel 字段只加权限也行,但那样必须靠代码动态指定页面,对新手不友好,我建议先写上默认值。

30-3 侧边栏页面就是普通网页

sidepanel.html 没有任何特殊语法,和你写 popup、options 页一样:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="sidepanel.css" />
  </head>
  <body>
    <h1>我的侧边栏</h1>
    <p id="tip">当前页面标题会显示在这里。</p>
    <button id="refresh">刷新</button>
    <script src="sidepanel.js"></script>
  </body>
</html>

配套的脚本也是普通 JS,能用全部扩展 API:

const tip = document.getElementById('tip');

async function showActiveTabTitle() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  tip.textContent = tab?.title ?? '读不到标题';
}

document.getElementById('refresh').addEventListener('click', showActiveTabTitle);
showActiveTabTitle();

样式上有一点要注意:侧边栏宽度很窄,通常只有三百多像素,而且用户能拖动改宽。所以别写死宽度,用百分比和弹性布局,让内容自己适配。

body {
  margin: 0;
  padding: 12px;
  width: 100%;
  box-sizing: border-box;
  font: 14px/1.6 system-ui, sans-serif;
}
Tip

侧边栏里的脚本受和其他扩展页面一样的 CSP 约束:不能写内联 onclick,不能加载远程 JS。所有逻辑放独立 .js 文件里引进来。

30-4 让点击图标就打开侧边栏

默认情况下,用户得右键图标才能打开侧边栏,这个路径太深。更常见的做法是:点一下工具栏图标就展开侧边栏

sidePanel.setPanelBehavior() 打开这个开关,写在后台的服务工作者(Service Worker)里:

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

openPanelOnActionClick: true 的意思是:点击 action 图标时打开侧边栏,而不是触发 action.onClicked 或弹出 popup。

也可以只在安装时设一次:

chrome.runtime.onInstalled.addListener(() => {
  chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
});
Warning

如果清单里 action 同时配了 default_popup,popup 优先级更高,点图标还是弹 popup。想让图标专门开侧边栏,就别给 action 配 default_popup,只留 default_title

30-5 代码里主动打开:sidePanel.open()

有时候你希望在别的交互里把侧边栏拉出来,比如用户点了右键菜单里的”发送到侧边栏”。这时用 chrome.sidePanel.open()

它接收一个对象,二选一:给 windowId 表示在整个窗口打开,给 tabId 表示只在这个标签页打开。

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'openSidePanel',
    title: '在侧边栏中打开',
    contexts: ['all']
  });
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === 'openSidePanel') {
    // 在当前窗口的所有页面打开侧边栏
    chrome.sidePanel.open({ windowId: tab.windowId });
  }
});

如果只想对当前这一个标签页生效,并且换一个专属页面:

chrome.runtime.onMessage.addListener((message, sender) => {
  (async () => {
    if (message.type === 'open_side_panel') {
      await chrome.sidePanel.open({ tabId: sender.tab.id });
      await chrome.sidePanel.setOptions({
        tabId: sender.tab.id,
        path: 'sidepanel-tab.html',
        enabled: true
      });
    }
  })();
});

这里有个坑要单独说:open() 必须由用户手势触发。也就是说,它得跟在一次点击、一次快捷键、一次右键菜单点击之后调用。你在 onInstalled 里或者定时器里直接调,浏览器会拒绝执行。

Note

注意上面 onMessage 回调的写法:里面套了一个立即执行的 async 函数,而外层监听器本身不返回 true。因为这次不需要回消息给发送方,返回假值才是正确做法。这个细节在消息通信那章讲过。

30-6 按标签页切换:setOptions 与 getOptions

侧边栏最实用的能力是”看什么网站,显示什么面板”。做法是监听标签页更新,用 setOptions() 改路径或直接禁用。

const TARGET_ORIGIN = 'https://www.example.com';

chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
  if (!tab.url) return;

  const url = new URL(tab.url);
  if (url.origin === TARGET_ORIGIN) {
    // 在目标站点启用侧边栏
    await chrome.sidePanel.setOptions({
      tabId,
      path: 'sidepanel.html',
      enabled: true
    });
  } else {
    // 其他站点关掉
    await chrome.sidePanel.setOptions({
      tabId,
      enabled: false
    });
  }
});

setOptions() 的三个常用参数:

  • tabId:只对这个标签页生效。不传就是改全局默认,会影响所有标签页。
  • path:这个标签页要加载的 HTML。
  • enabledfalse 时该标签页的侧边栏被禁用,用户在这里打不开。

想读回当前配置就用 getOptions()。比如实现”第一次打开显示欢迎页,之后换成主页面”:

const welcomePage = 'sidepanels/welcome.html';
const mainPage = 'sidepanels/main.html';

chrome.runtime.onInstalled.addListener(() => {
  chrome.sidePanel.setOptions({ path: welcomePage });
  chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
});

chrome.tabs.onActivated.addListener(async ({ tabId }) => {
  const { path } = await chrome.sidePanel.getOptions({ tabId });
  if (path === welcomePage) {
    chrome.sidePanel.setOptions({ path: mainPage });
  }
});
Tip

注意优先级:标签页级别的设置盖过全局设置。所以做站点专属面板时,务必在离开目标站点时显式 enabled: false,否则用户带着面板跑到别的网站,内容对不上。

30-7 几个新手常踩的坑

第一,忘了加 sidePanel 权限。控制台报 Cannot read properties of undefined,十次有九次是这个原因。

第二,指望侧边栏”永远活着”。用户手动关闭侧边栏后,页面会被销毁,里面的 JS 变量全部丢失。需要跨开关保留的数据,请写进 chrome.storage,重新打开时再读回来。

第三,把侧边栏当后台用。它不是常驻进程,也不该承担定时任务。真要定时干活,交给服务工作者配 chrome.alarms

第四,样式没做窄屏适配。侧边栏比手机屏还窄,横向溢出的表格、固定宽度的卡片在这里全都难看。开发时把面板拖到最窄再看一遍。

第五,在非用户手势里调 open()。前面提过一次,这里再强调:它必须紧跟一次真实的用户操作。常见的错误写法是把 open() 放进 setTimeout 或者放在一段 await 之后,等待过程中手势凭证就失效了。要先开面板,再做异步的活。

第六,调试时找不到侧边栏的控制台。它和 popup 一样是独立页面:在侧边栏区域点右键选择检查,就会弹出专属的开发者工具窗口。别去服务工作者的控制台里翻侧边栏的日志,两者不是一个上下文。

30-8 小结

侧边栏的完整链路是这样的:清单里加 sidePanel 权限 + side_panel.default_path,写一个普通 HTML 页面,后台用 setPanelBehavior 让图标点击即开,需要时用 open() 主动拉出,再用 setOptions() / getOptions() 按标签页调度页面。

选它还是选 popup,看交互时长:一次性操作给 popup,长期伴随给侧边栏。下一节我们讲通知 notifications——扩展怎么在浏览器之外,用系统级通知提醒用户。