
1. 背景与核心概念你是否曾觉得B站网页版的默认界面有些单调或者某些功能操作不够便捷比如想快速下载一个视频、想一键查看UP主的详细“成分”、或者只是想修改一下播放器的快捷键如果你有过这些想法那么浏览器插件Extension就是你实现这些个性化需求的最佳工具。本文将围绕“如何从零开始开发一个B站美化插件”展开这是一个集前端技术、浏览器API和逆向工程于一体的综合性实战项目。一个浏览器插件本质上是一个运行在浏览器环境中的小型Web应用程序。它可以通过注入脚本Content Scripts、修改页面样式CSS、创建独立界面Popup等方式与特定的网页如B站进行深度交互从而增强或改变其原有功能。我们常说的“美化插件”通常包含但不限于以下功能界面样式自定义如暗色模式、精简布局、功能增强如视频下载、快捷键修改、信息聚合与展示如“查成分”工具显示UID注册时间、粉丝数变化等。对于前端开发者而言开发一个浏览器插件是巩固HTML、CSS、JavaScript知识的绝佳实践同时也能深入理解浏览器的工作原理和Web安全策略。对于普通用户掌握插件开发能力意味着你可以亲手打造最适合自己使用习惯的工具。本文将带你从环境搭建、核心原理、代码实战到打包发布完整走一遍Chrome插件同样兼容Edge等Chromium内核浏览器的开发流程。我们将以实现几个典型功能为例构建一个功能丰富的B站增强插件。2. 环境准备与版本说明开发浏览器插件不需要复杂的后端环境核心工具就是你的浏览器和代码编辑器。以下是本次实战的环境清单浏览器Google Chrome 或 Microsoft Edge版本100以上均可建议使用最新稳定版。我们将使用Chrome的开发者模式进行加载和调试。代码编辑器Visual Studio Code (VS Code) 或其他你熟悉的编辑器如WebStorm, Sublime Text。前端基础需要具备HTML、CSS和JavaScript (ES6) 的基础知识。本项目不依赖React/Vue等框架使用原生JS和现代ES语法。Node.js (可选)如果你计划使用构建工具如Webpack、Vite来管理模块或压缩代码则需要安装Node.js。对于入门插件我们可以不使用构建工具以保持简单。调试工具浏览器自带的开发者工具F12。项目结构预览 在开始编码前我们先规划一个清晰的目录结构这对于插件开发至关重要。bilibili-enhancer-extension/ ├── manifest.json # 插件核心配置文件 ├── popup.html # 插件弹出窗口的页面 ├── popup.js # 弹出窗口的交互逻辑 ├── popup.css # 弹出窗口的样式 ├── content.js # 注入到B站页面的核心脚本 ├── content.css # 注入到B站页面的样式 ├── background.js # 后台服务脚本用于长期运行或跨页面通信 ├── icons/ # 插件图标文件夹 │ ├── icon16.png │ ├── icon48.png │ └── icon128.png └── _locales (可选) # 国际化文件夹 └── zh_CN └── messages.json版本说明本文示例基于Manifest V3规范。Manifest V3是Chrome插件的最新规范它更注重安全性和隐私与之前的V2在部分API如网络请求、后台脚本上有较大差异。新开发的插件应优先使用V3。我们将使用V3来构建我们的插件。3. 核心语法、配置或原理拆解3.1 Manifest.json插件的大脑manifest.json是每个插件的必备文件它定义了插件的基本信息、权限、资源以及如何与浏览器和网页交互。{ manifest_version: 3, name: B站助手 - 美化与增强, version: 1.0.0, description: 提供B站界面美化、视频下载、用户信息查询等增强功能。, permissions: [ storage, activeTab, scripting ], host_permissions: [ https://*.bilibili.com/* ], action: { default_popup: popup.html, default_icon: { 16: icons/icon16.png, 48: icons/icon48.png, 128: icons/icon128.png } }, content_scripts: [ { matches: [https://*.bilibili.com/*], js: [content.js], css: [content.css], run_at: document_idle } ], background: { service_worker: background.js }, icons: { 16: icons/icon16.png, 48: icons/icon48.png, 128: icons/icon128.png } }关键字段解释manifest_version: 必须为3。permissions: 声明插件需要的权限。storage用于本地存储用户设置activeTab和scripting用于在用户与页面交互时执行脚本。host_permissions: 声明插件可以访问的网站这里我们允许访问所有B站域名下的页面。action: 定义浏览器工具栏图标的行为。default_popup指定点击图标后弹出的页面。content_scripts:这是实现页面修改的关键。它定义了在哪些页面matches自动注入哪些JS和CSS文件。run_at设置为document_idle表示在页面加载完成后注入避免影响页面加载性能。background.service_worker: 后台脚本用于处理事件监听、跨页面通信等生命周期独立于任何网页。3.2 Content Script与页面交互的桥梁Content Script内容脚本运行在网页的上下文中可以访问和操作页面的DOM文档对象模型就像网页自己的脚本一样但它运行在一个独立的“隔离环境”中不能直接访问网页JavaScript的变量和函数反之亦然。通信需要通过chrome.runtime.sendMessage等API。主要能力DOM操作增删改查页面元素。样式注入通过添加style标签或修改元素的class来改变页面外观。事件监听监听页面上的点击、输入等事件。数据提取从页面结构中抓取信息如视频标题、UP主UID、弹幕等。3.3 Popup与Background插件的UI与后台Popup一个独立的HTML页面当用户点击工具栏图标时弹出。它适合放置插件的设置面板、功能开关等交互界面。Popup的JS可以调用大部分Chrome API。Background Service Worker一个长期运行的脚本用于管理插件的状态、监听浏览器事件如安装、标签页更新、处理跨标签页通信或执行定时任务。在V3中它是非持久化的会在需要时唤醒不活动时休眠以节省资源。3.4 通信机制连接各个部分插件各部分之间需要通信来协同工作。Content Script - Background使用chrome.runtime.sendMessage和chrome.runtime.onMessage.addListener。Popup - Background通信方式同上。Content Script - Web Page由于隔离环境不能直接通信。如果需要调用网页全局函数必须通过window.postMessage和window.addEventListener(‘message‘, ...)进行间接通信这需要网页端也有相应的监听代码对于B站我们可以注入脚本到页面上下文来实现。4. 完整实战案例B站美化与功能增强插件现在我们开始一步步实现插件。我们将实现三个核心功能① 界面美化暗色模式增强② 快捷键修改③ 简易“查成分”信息展示。4.1 创建项目结构与基础配置首先按照之前规划的目录结构创建所有文件和文件夹。确保icons文件夹中有几个简单的PNG图标可以用在线工具生成尺寸为16x16, 48x48, 128x128。manifest.json文件使用上面提供的配置。4.2 实现界面美化 (content.css)我们将为B站首页和视频播放页增加一个更深的暗色主题。/* content.css - 自定义B站深色模式 */ /* 覆盖B站原有的--bg1等CSS变量实现更深背景 */ html[data-themedark] { --bg1: #0a0a0f !important; --bg2: #141421 !important; --bg3: #1c1c2d !important; --bg-border: #2a2a40 !important; } /* 美化视频卡片增加圆角和阴影 */ .bili-video-card, .video-card-common { border-radius: 12px !important; overflow: hidden !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important; transition: transform 0.2s ease, box-shadow 0.2s ease !important; } .bili-video-card:hover { transform: translateY(-4px) !important; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4) !important; } /* 简化播放器控制栏隐藏不常用的按钮示例隐藏“宽屏”按钮 */ .bpx-player-ctrl-wide { display: none !important; } /* 自定义滚动条 */ ::-webkit-scrollbar { width: 10px; } ::-webkit-scrollbar-track { background: var(--bg1); } ::-webkit-scrollbar-thumb { background: var(--bg3); border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { background: #3a3a5c; }4.3 实现快捷键修改与功能注入 (content.js)我们将修改B站播放器的空格键行为默认空格键是“播放/暂停”我们将其改为“播放/暂停”和“触发页面滚动”的智能切换。当焦点在播放器上时空格用于播放/暂停当焦点在页面其他部分如评论区时空格恢复正常的滚动功能。// content.js (function() { use strict; // 1. 监听键盘事件 document.addEventListener(keydown, function(event) { // 检查按下的键是否是空格键 if (event.code Space) { // 获取当前活动的元素 const activeElement document.activeElement; const isPlayerFocused activeElement.tagName VIDEO || activeElement.classList.contains(bpx-player-video-wrap) || activeElement.closest(.bpx-player-control) ! null; // 如果焦点在播放器相关区域则阻止默认行为滚动由播放器自己处理播放/暂停 if (isPlayerFocused) { // 什么都不做让播放器的原生逻辑处理 console.log([B站助手] 空格键用于播放/暂停); } else { // 如果焦点不在播放器上我们允许空格键的默认行为滚动 // 但可以在这里添加其他逻辑例如阻止在输入框内按空格变成输入空格 if (activeElement.tagName INPUT || activeElement.tagName TEXTAREA) { // 在输入框内我们也不阻止让其正常输入空格 return; } // 对于其他情况允许滚动。实际上不需要做任何事。 console.log([B站助手] 空格键用于页面滚动); } } }, true); // 使用捕获阶段确保我们先于页面脚本处理 // 2. 注入“查成分”按钮到用户主页 function injectUserProfileButton() { // 等待页面主体加载寻找用户信息区域 const observer new MutationObserver(() { const profileElement document.querySelector(.up-info); // B站用户主页信息区域的选择器可能变化 if (profileElement !profileElement.querySelector(.bili-helper-btn)) { const uid window.location.pathname.match(/\/space\.bilibili\.com\/(\d)/)?.[1]; if (uid) { const btn document.createElement(button); btn.className bili-helper-btn; btn.innerHTML 查看详细成分; btn.style.cssText margin-left: 10px; padding: 4px 12px; background: linear-gradient(135deg, #00a1d6, #6f42c1); color: white; border: none; border-radius: 15px; cursor: pointer; font-size: 12px;; btn.onclick () showUserDetail(uid); profileElement.appendChild(btn); } } }); observer.observe(document.body, { childList: true, subtree: true }); } // 3. 显示用户详情函数模拟 function showUserDetail(uid) { // 这里本应调用B站API或自己的后端但受限于CORS和API限制我们仅做演示。 // 实际项目中你可能需要一个后端代理或使用其他数据源。 alert(用户UID: ${uid}\n功能演示此处可显示注册时间、粉丝增长曲线、常用标签等。\n注实际数据获取需要处理接口和反爬机制); // 更优雅的方式是创建一个浮层来显示信息 createDetailPanel(uid); } function createDetailPanel(uid) { // 创建并插入一个信息面板到页面 const panel document.createElement(div); panel.id bili-helper-detail-panel; panel.style.cssText position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--bg2); border: 1px solid var(--bg-border); padding: 20px; border-radius: 10px; z-index: 10000; box-shadow: 0 10px 30px rgba(0,0,0,0.5); min-width: 300px;; panel.innerHTML h3 stylemargin-top:0;用户成分分析 (UID: ${uid})/h3 p注册时间span idreg-time获取中.../span/p p粉丝数span idfans获取中.../span/p p常用标签span idtags游戏、科技、生活/span/p hr p stylecolor: #888; font-size: 0.9em;* 此为演示数据真实功能需处理API。/p button onclickthis.parentNode.remove() stylefloat:right; padding:5px 15px;关闭/button ; document.body.appendChild(panel); // 模拟异步获取数据 setTimeout(() { document.getElementById(reg-time).textContent 2020-03-15 (模拟); document.getElementById(fans).textContent 154,321 (模拟); }, 500); } // 4. 初始化 window.addEventListener(load, () { injectUserProfileButton(); console.log([B站助手] 内容脚本加载完毕。); }); })();4.4 实现Popup设置界面 (popup.html, popup.js, popup.css)popup.html提供了一个简单的用户界面用于控制插件功能。!DOCTYPE html html head meta charsetutf-8 link relstylesheet hrefpopup.css /head body div classcontainer h2B站助手设置/h2 div classsection label classswitch input typecheckbox idtoggleDarkEnhance span classslider/span /label span classlabel启用深度暗色模式/span /div div classsection label classswitch input typecheckbox idtoggleSmartSpace span classslider/span /label span classlabel启用智能空格键/span /div div classsection label classswitch input typecheckbox idtoggleProfileBtn span classslider/span /label span classlabel显示“查成分”按钮/span /div hr div classsection p快捷键自定义开发中/p input typetext idcustomKey placeholder例如: CtrlShiftD disabled /div button idsaveBtn保存设置/button p idstatus/p /div script srcpopup.js/script /body /html/* popup.css */ body { width: 300px; padding: 15px; font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; background-color: #1e1e2e; color: #cdd6f4; margin: 0; } .container { display: flex; flex-direction: column; gap: 15px; } h2 { margin-top: 0; color: #89b4fa; border-bottom: 1px solid #313244; padding-bottom: 8px; } .section { display: flex; align-items: center; gap: 10px; } .switch { position: relative; display: inline-block; width: 50px; height: 24px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #45475a; transition: .4s; border-radius: 24px; } .slider:before { position: absolute; content: ; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: #cdd6f4; transition: .4s; border-radius: 50%; } input:checked .slider { background-color: #89b4fa; } input:checked .slider:before { transform: translateX(26px); } .label { font-size: 14px; } #customKey { flex-grow: 1; padding: 5px; background: #313244; border: 1px solid #45475a; border-radius: 4px; color: inherit; } #saveBtn { background: linear-gradient(135deg, #74c7ec, #89b4fa); color: #1e1e2e; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; transition: opacity 0.2s; } #saveBtn:hover { opacity: 0.9; } #status { font-size: 12px; color: #a6adc8; text-align: center; min-height: 16px; }// popup.js document.addEventListener(DOMContentLoaded, function() { // 从存储中加载设置 chrome.storage.sync.get({ darkEnhance: true, smartSpace: true, profileBtn: true }, function(items) { document.getElementById(toggleDarkEnhance).checked items.darkEnhance; document.getElementById(toggleSmartSpace).checked items.smartSpace; document.getElementById(toggleProfileBtn).checked items.profileBtn; }); // 保存设置 document.getElementById(saveBtn).addEventListener(click, function() { const settings { darkEnhance: document.getElementById(toggleDarkEnhance).checked, smartSpace: document.getElementById(toggleSmartSpace).checked, profileBtn: document.getElementById(toggleProfileBtn).checked }; chrome.storage.sync.set(settings, function() { const status document.getElementById(status); status.textContent 设置已保存刷新B站页面生效。; setTimeout(() { status.textContent ; }, 2000); // 通知content script设置已更新 chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { if (tabs[0] tabs[0].url.includes(bilibili.com)) { chrome.tabs.sendMessage(tabs[0].id, {action: settingsUpdated, settings: settings}); } }); }); }); });4.5 实现后台脚本与通信 (background.js)后台脚本主要用于监听安装事件、管理跨标签页通信本例中简化。// background.js - Manifest V3 使用 Service Worker chrome.runtime.onInstalled.addListener(() { console.log(B站助手插件已安装/更新。); // 设置默认值 chrome.storage.sync.set({ darkEnhance: true, smartSpace: true, profileBtn: true }); }); // 监听来自content script或popup的消息 chrome.runtime.onMessage.addListener((request, sender, sendResponse) { console.log(Background收到消息, request); // 可以在这里处理一些全局逻辑例如转发消息 if (request.action getSettings) { chrome.storage.sync.get(null, sendResponse); return true; // 表示将异步发送响应 } });4.6 加载插件与运行验证打开Chrome浏览器在地址栏输入chrome://extensions/并回车。打开右上角的“开发者模式”开关。点击左上角的“加载已解压的扩展程序”按钮。选择你创建的bilibili-enhancer-extension项目文件夹。插件会出现在扩展列表中。确保其开关是打开的。验证步骤访问www.bilibili.com。点击浏览器工具栏上的插件图标弹出设置窗口尝试开关选项并保存。刷新B站页面观察变化页面背景、卡片是否变得更暗、更有立体感content.css生效在视频播放页尝试在播放器区域外按空格页面应正常滚动点击播放器后按空格应能控制播放/暂停。content.js生效进入一个UP主的空间页如space.bilibili.com/123456在UP主信息区域附近是否看到一个“查看详细成分”的按钮点击它是否会弹出信息面板content.js生效检查控制台F12 - Console查看是否有[B站助手]开头的日志。至此一个具备基础美化、快捷键调整和简单信息展示功能的B站插件就完成了。你可以通过修改CSS和JS来不断添加新功能。5. 常见问题与排查思路在开发和使用插件过程中你可能会遇到以下问题问题现象常见原因解决思路插件图标未显示在工具栏1.manifest.json中action配置错误或缺失。2. 图标文件路径错误或尺寸不符。1. 检查manifest.json的action字段。2. 确认icons文件夹内图片存在且命名正确。在扩展管理页面点击“详细信息”查看错误。Content Script 未注入/未生效1.manifest.json中content_scripts的matchesURL模式不匹配当前页面。2. JS/CSS文件路径错误。3. 脚本执行时机 (run_at) 不合适。1. 确认访问的B站网址如https://www.bilibili.com/video/BV1xx...能被https://*.bilibili.com/*匹配。2. 检查content.js和content.css是否在根目录。3. 尝试将run_at改为document_end。在F12控制台的Sources标签页查看是否加载了你的脚本。Popup页面打开是空白1.popup.html文件路径错误。2. HTML文件内有语法错误导致无法解析。3. 引用的JS/CSS路径错误。1. 检查manifest.json中default_popup路径。2. 右键点击Popup选择“检查”在开发者工具中查看Console和Network标签页的错误信息。保存设置后页面不刷新/不生效1.popup.js中发送给content script的消息未正确接收。2.content script没有监听chrome.runtime.onMessage事件。3. 页面需要刷新才能应用新的CSS或初始化逻辑。1. 在content.js中添加消息监听器并打印接收到的消息。2. 确保chrome.tabs.sendMessage的目标标签页ID正确。3. 提示用户刷新页面或在content script中动态应用新设置。功能在部分B站页面无效B站不同页面主站、直播、专栏的DOM结构不同选择器失效。1. 使用更通用的选择器或多种选择器备用。2. 使用MutationObserver监听DOM变化动态注入元素。3. 在content_scripts的matches中细化需要注入的页面模式。插件更新后旧配置丢失未使用chrome.storage持久化存储或存储区域选择不当。使用chrome.storage.sync跨设备同步或chrome.storage.local本地存储来保存用户设置。并在插件加载时读取。通用排查流程打开开发者工具在插件管理页面点击“服务工作者”链接查看background日志在目标网页F12查看Console中content script的日志和错误。检查ManifestJSON格式是否正确权限是否声明。检查文件路径所有在manifest.json中引用的文件路径是否准确。重载插件在扩展管理页面点击插件卡片上的刷新图标然后刷新目标网页。6. 最佳实践与工程建议将一个小demo变成可维护、可发布的插件还需要注意以下工程化细节模块化与构建对于复杂插件建议使用如Webpack或Vite进行构建。这允许你使用ES6模块、NPM包、压缩代码等。将content.js拆分为多个按功能划分的模块如dom-injector.js,keyboard-manager.js,api-fetcher.js。安全与隐私最小权限原则在manifest.json中只申请必要的权限。例如如果不需要访问所有网站就不要用all_urls。清理动态节点通过content script动态添加到页面的元素如浮层、按钮在插件卸载或页面关闭时应尽可能清理避免内存泄漏。谨慎处理用户数据如果插件需要收集用户数据如设置明确告知用户并遵守相关隐私政策。避免收集敏感信息。错误处理与日志在content.js和background.js中使用try...catch包裹关键操作。提供有意义的日志并考虑通过chrome.storage记录错误报告用户可选。在Popup中提供错误状态反馈。用户配置使用chrome.storage管理配置并提供重置默认值的选项。配置变更时及时通知content script避免用户必须手动刷新页面。兼容性与降级B站前端经常更新CSS选择器和DOM结构可能变化。你的代码需要有韧性使用更稳定的选择器如>