侧边栏 side_panel
本教程共 56 篇 · 第 30 篇 · 更新于 2026-08-13 · 约 7 分钟阅读
本节目标:学完你能给扩展加一个停靠在浏览器右侧的侧边栏,会用清单声明默认页面,也会用 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。enabled:false时该标签页的侧边栏被禁用,用户在这里打不开。
想读回当前配置就用 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——扩展怎么在浏览器之外,用系统级通知提醒用户。