我写了一个 Chrome 扩展,让任何网站都能"换皮":架构、踩坑与设计实录

举报
yd_294377644 发表于 2026/10/18 16:52:47 2026/10/18
【摘要】 有些网站内容很好,但 UI 看着让人累——字号太小、对比度刺眼、广告位抢眼、暗色模式形同虚设。市面上的样式定制扩展要么功能臃肿要么带云端同步让人不放心。于是我写了一个**纯本地、按域名匹配、一键注入自定义 CSS** 的 Chrome 扩展,专门用来"让自己浏览时更舒服"。这篇博客记录从需求到能用的全过程:扩展架构、Manifest V3 适配、注入时机、CSS 优先级博弈,以及那些文档没告诉你的

目录

  1. 缘起:为什么自己写一个"换皮"扩展
  2. 需求拆解:从"能改样式"到"用起来舒服"
  3. Chrome 扩展架构:四个文件就够
  4. 核心实现:按域名注入自定义 CSS
  5. Manifest V3 适配:那些文档没明说的限制
  6. 三个让我卡了半天的坑
  7. 交互设计:让"改样式"这件事本身不累
  8. 结尾:工具型扩展的"克制"哲学

1. 缘起:为什么自己写一个"换皮"扩展

我有一批每天要打开的网站:某个文档站、某个内部系统、某个资讯站。内容都不错,但 UI 各有各的"不舒服"——

  • 文档站字号 14px,长文看着费眼;
  • 内部系统主色是高饱和蓝,盯半小时就累;
  • 资讯站广告位比正文还显眼。

我试过现成的样式定制扩展,但要么功能多到打开就劝退,要么默认把样式同步到云端账号——我只想改自己电脑上几个站的样式,不想注册不想上传。

于是目标定了:做一个纯本地的 Chrome 扩展,按域名存自定义 CSS,访问对应网站时自动注入,让我浏览时更舒服。

2. 需求拆解:从"能改样式"到"用起来舒服"

和 AI 协作的第一步,我把"能改样式"拆成了可验收的清单:

诉求(人话) 拆解后(可验收)
能改样式 提供编辑框,保存自定义 CSS 并注入到当前网站
按网站分别存 以域名为 key 存到 chrome.storage,不同站点互不干扰
能开关 每条样式有启用/禁用开关,不必删除就能临时关掉
不丢 数据存本地,重装浏览器可导出/导入 JSON 备份
用着不累 弹出面板默认显示当前域名,一键进入编辑,保存即生效

这张表直接决定了扩展的骨架——功能不多,但每一项都做到位。

3. Chrome 扩展架构:四个文件就够

Chrome 扩展听起来复杂,其实最小可用版本就四个文件:

my-style-extension/
 ├─ manifest.json    // 扩展元数据与权限声明
 ├─ content.js       // 注入到页面、应用 CSS 的脚本
 ├─ popup.html       // 点击扩展图标弹出的小面板
 └─ popup.js         // 面板逻辑:编辑、保存、开关

manifest.json 是核心声明(Manifest V3 版本):

{
  "manifest_version": 3,
  "name": "本地样式定制",
  "version": "1.0.0",
  "permissions": ["storage", "activeTab", "scripting"],
  "host_permissions": ["<all_urls>"],
  "action": { "default_popup": "popup.html" },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "run_at": "document_start"
    }
  ]
}

心得:扩展开发的门槛其实不在"写代码",而在"搞清楚每个权限到底要不要声明"。host_permissions 和 scripting 的区别、activeTab 和 <all_urls> 的取舍,是初学者最容易绕晕的地方。

4. 核心实现:按域名注入自定义 CSS

核心逻辑只有两段:存的时候按域名存,注入的时候按当前域名取。

popup.js 保存(按当前域名写入 storage):

chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
    const domain = new URL(tab.url).hostname;
    chrome.storage.local.get('rules', ({ rules = {} }) => {
        rules[domain] = { css: editor.value, enabled: true };
        chrome.storage.local.set({ rules });
    });
});

content.js 注入(页面加载时取当前域名的规则):

const domain = location.hostname;
chrome.storage.local.get('rules', ({ rules = {} }) => {
    const rule = rules[domain];
    if (rule && rule.enabled && rule.css) {
        const style = document.createElement('style');
        style.textContent = rule.css;
        document.head.appendChild(style);
    }
});

就这么几行,扩展就能跑了。Chrome 扩展的"魔法"其实很朴素:content script 是注入到页面的 JS,chrome.storage 是本地 KV 存储,两者一组合就是"按域名持久化样式"的完整闭环。

5. Manifest V3 适配:那些文档没明说的限制

我从 V2 起步,迁到 V3 时踩了几个文档没明说的点:

  1. browser_action 改名 action,page_action 被合并进 action。V2 的扩展不改这部分直接装不上;
  2. chrome.tabs.executeScript 被废弃,改用 chrome.scripting.executeScript,且需要在 permissions 里加 "scripting";
  3. 远程代码执行被禁。V3 不允许 content script 引入远程 JS,所有逻辑必须打包进扩展。这对"纯本地"扩展反而是好事——逼着你把代码写干净。

心得:Manifest V3 的核心变化其实就一句话——更安全、更本地、更不可远程操控。对一个"本地改样式"的扩展来说,V3 的限制反而是天然契合的。

6. 三个让我卡了半天的坑

坑一:注入时机决定一切。 一开始 run_at 用默认的 document_idle,结果某些站点在 document_idle 之前就已经渲染完并应用了自己的样式,我注入的 <style> 虽然在 DOM 里,但被站点后续 JS 动态注入的样式覆盖。改成 document_start 后,我的 <style> 提前插入,配合 !important 才稳定生效。教训:注入样式类扩展,run_at 选 document_start,再用 !important 兜底。

坑二:CSS 优先级博弈。 网站的样式表会写 !important,你的也写 !important,最后比的是选择器特异性。我一开始用 body { ... } 这种低特异性选择器,被站点秒杀。后来养成习惯:自定义样式一律用高特异性选择器 + !important,比如 html body div.main .content { ... !important },胜率才上来。

坑三:CSP 限制动态注入。 某些站点设了严格的 Content-Security-Policy,直接 document.head.appendChild(style) 会被拦。解决办法是用 chrome.scripting.insertCSS API 注入——这是扩展特权 API,不受页面 CSP 约束。教训:能走扩展 API 就别走 DOM 注入,少踩一半坑。

7. 交互设计:让"改样式"这件事本身不累

扩展本身也要好用,否则就是"为了改样式又给自己添麻烦"。我的 popup 面板设计原则只有一条:打开就能改,改完即生效。

  • 打开 popup 自动显示当前域名和已保存的样式;
  • 编辑框默认聚焦,Ctrl+S 保存并立即注入到当前页(不用刷新);
  • 顶部一个"启用/禁用"开关,临时关掉某站样式不用删;
  • 底部"导出/导入"按钮,备份和迁移用。

心得:工具型扩展的交互衡量标准是"完成一次定制需要几次点击"。我的目标是 3 次以内:打开扩展 → 改 CSS → Ctrl+S。每多一次点击,就多一个不再使用的理由。

8. 结尾:工具型扩展的"克制"哲学

做完这个扩展,我对"工具型产品"有了一个更清晰的认知:

  1. 功能克制:我没做"样式市场"“一键应用别人分享的样式”“云端同步”——不是做不到,是每加一个功能,主流程就重一分;
  2. 数据本地:所有样式存在 chrome.storage.local,不发起任何网络请求。隐私是这类工具的底线;
  3. 可迁移:导入导出 JSON 是从第一天就有的,而不是最后补的——数据主权要写进架构,不是写进营销文案。

如果你也想做类似的小工具,我的建议是:从一个自己每天都会用的场景出发,把"用着不累"当成和"功能正确"一样硬的需求。Chrome 扩展的门槛比想象中低,四个文件就能跑;真正难的是"克制住加功能的冲动"。

工具小而准,价值往往不在多,而在"每次打开都恰好够用"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。