
你是不是也经常在B站看视频时觉得网页版界面有些地方“差点意思”比如想快速调整播放速度但快捷键不好记想一键下载视频却找不到官方入口或者单纯觉得播放器控件太占地方想让它更简洁今天要聊的不是一个简单的“换肤”工具而是一个能深度定制你B站浏览体验的浏览器插件。它解决的远不止是“美化”问题而是将一系列高频但分散的用户需求通过技术手段整合到了一个统一的入口里。很多人以为美化插件就是改改颜色、调调布局。但一个真正有价值的插件其核心在于功能增强与效率提升。它应该像一个得力的助手在你最需要的时候提供最顺手的工具。本文将从一个开发者自研插件的视角带你深入理解如何从零构建一个功能全面的B站增强插件。你将不仅学会如何实现几个炫酷功能更能掌握浏览器插件开发的核心流程、与网页交互的关键技术以及如何安全、合规地设计这类工具。1. 这个插件到底解决了什么问题在开始敲代码之前我们必须明确我们不是在“破解”或“破坏”B站而是在其官方网页的基础上进行合法的、非侵入式的功能增强。这就像给你的汽车加装一个更智能的行车记录仪而不是去篡改发动机。这个自研插件主要瞄准了B站网页版用户的几个核心痛点操作效率低下官方播放器的快捷键系统不够直观调整速度、音量、清晰度需要多次点击或记忆复杂组合键。功能缺失B站网页版不提供视频下载功能用户有时需要保存一些教程或珍贵素材以供离线学习。信息获取不便想了解一个UP主的“成分”注册时间、活跃情况等需要手动拼接URL或使用第三方网站过程繁琐。界面个性化不足默认播放器控件、页面布局可能不符合所有用户的审美和操作习惯。因此我们的插件将围绕“快捷键优化”、“视频信息提取”、“界面微调”三大方向来设计功能。请注意所有功能的实现都必须严格遵守浏览器扩展的开发规范并尊重网站的服务条款仅用于个人学习与技术研究。2. 核心概念浏览器插件如何与网页交互在动手开发前理解浏览器插件Extension的基本架构至关重要。一个典型的插件由以下几部分组成Manifest (manifest.json)插件的“身份证”和“总说明书”定义了插件的基本信息、权限、需要注入的脚本和资源。Background Script (后台脚本)一个长期运行的脚本负责处理全局事件、管理插件状态、进行网络请求等。它独立于任何网页。Content Script (内容脚本)被注入到特定网页中的脚本可以直接访问和操作该网页的DOM文档对象模型。它是插件与网页交互的主力。Popup (弹出页面)点击插件图标时出现的小窗口通常用于提供快捷设置和操作界面。Options Page (选项页面)一个完整的HTML页面用于进行复杂的插件配置。它们之间的关系特别是内容脚本与网页的交互是开发此类增强插件的核心。内容脚本运行在网页的上下文中可以读取和修改页面内容但它与插件的其他部分如后台脚本、Popup是隔离的需要通过特定的API进行通信。3. 开发环境与工具准备我们将以 Chrome/Edge 浏览器扩展为例进行开发因为其生态最成熟且与新版Edge兼容。所需环境一台电脑Windows, macOS, Linux 均可任意现代浏览器推荐 Chrome 或 Edge一个文本编辑器或IDE如 VS Code不需要特殊的服务器或复杂的编译环境。浏览器插件开发本质上是前端技术HTML, CSS, JavaScript的应用。项目结构预览在开始前我们先规划一个清晰的目录结构bilibili-enhancer/ ├── manifest.json # 核心配置文件 ├── background.js # 后台脚本可选根据功能复杂度 ├── content.js # 内容脚本核心逻辑所在 ├── popup.html # 弹出窗口页面 ├── popup.js # 弹出窗口逻辑 ├── options.html # 选项页面可选 ├── options.js # 选项页面逻辑可选 └── icons/ # 插件图标文件夹 ├── icon16.png ├── icon48.png └── icon128.png4. 从零开始创建插件配置文件一切从manifest.json开始。这个文件告诉浏览器你的插件是谁能做什么。{ manifest_version: 3, name: B站体验增强助手, version: 1.0.0, description: 提供快捷键优化、界面微调等B站网页版增强功能。, permissions: [ storage, activeTab, scripting ], host_permissions: [ https://*.bilibili.com/* ], background: { service_worker: background.js }, content_scripts: [ { matches: [https://*.bilibili.com/*], js: [content.js], css: [content.css], run_at: document_end } ], action: { default_popup: popup.html, default_icon: { 16: icons/icon16.png, 48: icons/icon48.png, 128: icons/icon128.png } }, options_page: options.html }关键配置解释manifest_version: 3 使用最新的Manifest V3规范它更安全性能更好。permissions 声明插件需要的权限。storage用于保存用户设置activeTab和scripting用于在用户与页面交互时执行脚本。host_permissions 声明插件可以作用于哪些网站。这里我们限定为B站域名确保安全。content_scripts 这是核心。它定义了当用户访问任何bilibili.com下的网页时浏览器会自动将content.js和content.css注入到页面中。action 定义了浏览器工具栏上插件图标的行为点击会打开popup.html。5. 核心功能实现内容脚本开发content.js是我们插件的大脑所有与B站页面交互的逻辑都在这里。我们分模块实现功能。5.1 快捷键增强模块B站播放器本身有快捷键但我们可以让其更符合直觉或增加新功能。例如实现“按D键快速切换倍速”。// content.js - 快捷键增强模块 (function() { use strict; // 监听全局键盘事件 document.addEventListener(keydown, function(event) { // 确保事件发生在body上避免在输入框内触发 if (event.target.tagName INPUT || event.target.tagName TEXTAREA) { return; } const key event.key.toLowerCase(); const videoElement document.querySelector(video); if (!videoElement) return; // 当前页面没有视频 switch(key) { case d: // 切换倍速循环1.0 - 1.25 - 1.5 - 2.0 - 0.5 - 1.0 event.preventDefault(); const speeds [0.5, 1.0, 1.25, 1.5, 2.0]; let currentSpeed videoElement.playbackRate; let nextIndex (speeds.indexOf(currentSpeed) 1) % speeds.length; // 如果当前速度不在预设列表中则从1.0开始 if (nextIndex 0 speeds.indexOf(currentSpeed) -1) { nextIndex 1; } videoElement.playbackRate speeds[nextIndex]; showFloatingTip(播放速度: ${speeds[nextIndex]}x); break; case f: // 一键网页全屏非浏览器全屏 event.preventDefault(); const playerWrap document.querySelector(.bpx-player-video-wrap, .bilibili-player-video); if (playerWrap playerWrap.requestFullscreen) { playerWrap.requestFullscreen(); } break; // 可以继续添加更多自定义快捷键例如 [ 和 ] 调整进度 case [: event.preventDefault(); videoElement.currentTime Math.max(0, videoElement.currentTime - 10); showFloatingTip(-10秒); break; case ]: event.preventDefault(); videoElement.currentTime Math.min(videoElement.duration, videoElement.currentTime 10); showFloatingTip(10秒); break; } }); // 显示一个临时浮动提示 function showFloatingTip(text) { let tip document.getElementById(bili-enhancer-tip); if (!tip) { tip document.createElement(div); tip.id bili-enhancer-tip; Object.assign(tip.style, { position: fixed, top: 20%, left: 50%, transform: translateX(-50%), background: rgba(0, 0, 0, 0.8), color: #fff, padding: 10px 20px, borderRadius: 4px, zIndex: 10000, fontSize: 14px, fontFamily: sans-serif, pointerEvents: none, transition: opacity 0.3s }); document.body.appendChild(tip); } tip.textContent text; tip.style.opacity 1; setTimeout(() { tip.style.opacity 0; }, 1500); } console.log(B站增强插件 - 快捷键模块已加载); })();代码逻辑解析我们使用一个立即执行函数包裹代码避免污染页面的全局作用域。监听整个文档的keydown事件。通过event.target判断按键是否发生在输入框内避免干扰用户输入。找到页面中的video元素这是控制播放的核心。为D、F、[、]等键绑定自定义功能并使用event.preventDefault()阻止浏览器可能存在的默认行为。showFloatingTip函数提供了一个简单的视觉反馈让用户知道操作已生效。5.2 界面美化与微调模块这部分主要通过注入CSS样式来实现。我们创建一个content.css文件。/* content.css - 界面微调 */ /* 1. 隐藏播放器右侧的“短视频”挂件如果存在 */ .bilibili-player-video-suspension, .bpx-player-suspension { display: none !important; } /* 2. 让播放器控制栏在鼠标未悬停时更透明 */ .bpx-player-control-wrap, .bilibili-player-video-control { opacity: 0.4; transition: opacity 0.3s ease; } .bpx-player-control-wrap:hover, .bilibili-player-video-control:hover { opacity: 1; } /* 3. 简化视频标题栏减少冗余信息 */ .video-info .video-title { font-weight: bold; } /* 可以尝试隐藏一些推广模块但需谨慎选择选择器 */ /* .recommend-special, .ad-report { display: none; } */ /* 4. 自定义滚动条示例 */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #555; }注意事项使用!important需要谨慎它用于覆盖B站内联样式或更高优先级的选择器。B站的CSS类名可能会随版本更新而改变所以这类样式需要定期维护。美化应以“增强”而非“破坏”原功能为前提。5.3 信息增强模块示例显示UID注册时间这个功能需要与插件后台脚本配合因为获取外部数据如从第三方API查询UID信息通常在后台进行以避免跨域限制。首先在content.js中添加逻辑检测页面中的UID并向后端请求信息。// content.js - 信息增强模块部分逻辑 function enhanceUserInfo() { // 示例在UP主主页尝试从URL或页面元素中提取UID const uidMatch window.location.pathname.match(/\/space\.bilibili\.com\/(\d)/); let uid uidMatch ? uidMatch[1] : null; // 如果不是空间页尝试从当前视频页面的所有者信息获取 if (!uid) { const upLink document.querySelector(a.up-name, .video-up-info a); if (upLink upLink.href) { const upUidMatch upLink.href.match(/\/(\d)/); uid upUidMatch ? upUidMatch[1] : null; } } if (uid) { // 向插件的后台脚本发送消息请求查询该UID的信息 chrome.runtime.sendMessage({action: queryUidInfo, uid: uid}, function(response) { if (response response.success) { // 将获取到的信息如注册时间插入到页面合适位置 insertUidInfo(uid, response.data); } }); } } function insertUidInfo(uid, data) { // 找到一个合适的位置插入信息例如在UP主名字旁边 const container document.querySelector(.up-info, .video-up-info); if (!container) return; let infoBox document.getElementById(bili-enhancer-uid-info); if (!infoBox) { infoBox document.createElement(div); infoBox.id bili-enhancer-uid-info; infoBox.style.cssText font-size:12px; color:#999; margin-top:5px;; container.appendChild(infoBox); } // 假设 data 包含 registerTime infoBox.innerHTML UID: ${uid} | 注册于: ${data.registerTime || 未知}; } // 页面加载完成后执行并监听SPA页面变化如B站使用了PJAX enhanceUserInfo(); // 使用MutationObserver监听DOM变化在动态加载的内容中也能生效 const observer new MutationObserver(() { enhanceUserInfo(); }); observer.observe(document.body, { childList: true, subtree: true });然后在background.js中处理这个请求。请注意以下查询注册时间的API仅为示例实际中需要寻找稳定、合规的数据源且必须严格遵守相关网站的使用条款和速率限制。// background.js - 处理来自内容脚本的请求 chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.action queryUidInfo) { const uid request.uid; // !!! 重要此处仅为示例演示通信流程。实际调用第三方API需谨慎。 // 假设有一个虚构的、合规的查询接口 // fetch(https://api.example.com/bili/user/${uid}) // .then(response response.json()) // .then(data sendResponse({success: true, data: data})) // .catch(error sendResponse({success: false, error: error.message})); // 模拟返回数据 console.log(Background: Querying info for UID ${uid}); // 这里模拟一个延迟和返回结果 setTimeout(() { sendResponse({ success: true, data: { uid: uid, registerTime: 2016-03-15, // 模拟数据 // ... 其他信息 } }); }, 300); return true; // 保持消息通道开放用于异步响应 } });6. 构建用户界面Popup与Options Page6.1 弹出页面 (Popup)popup.html提供了一个快速开关和查看插件状态的入口。!DOCTYPE html html head meta charsetutf-8 style body { width: 200px; padding: 15px; font-family: sans-serif; } .section { margin-bottom: 15px; } h3 { margin-top: 0; font-size: 14px; } label { display: block; margin: 5px 0; font-size: 12px; } .status { font-size: 11px; color: green; margin-top: 10px; } /style /head body div classsection h3快捷键开关/h3 labelinput typecheckbox idtoggleShortcuts checked 启用自定义快捷键/label labelinput typecheckbox idtoggleFloatingTip checked 启用操作提示/label /div div classsection h3界面美化/h3 labelinput typecheckbox idtoggleMinimalUI 启用极简播放控件/label labelinput typecheckbox idtoggleCustomScrollbar 启用自定义滚动条/label /div div classsection button idsaveSettings保存设置/button button idopenOptions高级设置/button /div div idstatus classstatus就绪/div script srcpopup.js/script /body /html对应的popup.js负责与存储交互。// popup.js document.addEventListener(DOMContentLoaded, function() { // 从存储中加载设置 chrome.storage.sync.get({ shortcutsEnabled: true, floatingTipEnabled: true, minimalUI: false, customScrollbar: false }, function(items) { document.getElementById(toggleShortcuts).checked items.shortcutsEnabled; document.getElementById(toggleFloatingTip).checked items.floatingTipEnabled; document.getElementById(toggleMinimalUI).checked items.minimalUI; document.getElementById(toggleCustomScrollbar).checked items.customScrollbar; }); // 保存设置 document.getElementById(saveSettings).addEventListener(click, function() { const settings { shortcutsEnabled: document.getElementById(toggleShortcuts).checked, floatingTipEnabled: document.getElementById(toggleFloatingTip).checked, minimalUI: document.getElementById(toggleMinimalUI).checked, customScrollbar: document.getElementById(toggleCustomScrollbar).checked }; chrome.storage.sync.set(settings, function() { const status document.getElementById(status); status.textContent 设置已保存刷新B站页面生效。; setTimeout(() { status.textContent 就绪; }, 2000); }); }); // 打开选项页 document.getElementById(openOptions).addEventListener(click, function() { chrome.runtime.openOptionsPage(); }); });6.2 选项页面 (Options Page)选项页面options.html用于更复杂的配置例如自定义快捷键键位。!DOCTYPE html html head meta charsetutf-8 style/* 略可设计更复杂的样式 *//style /head body h2B站增强插件高级设置/h2 div classsetting-item label forspeedKey倍速切换键/label input typetext idspeedKey maxlength1 valued small单个字母/small /div div classsetting-item label forskipSeconds快进/快退秒数/label input typenumber idskipSeconds min1 max60 value10 秒 /div button idsaveOptions保存/button div idoptionsStatus/div script srcoptions.js/script /body /html// options.js document.addEventListener(DOMContentLoaded, function() { chrome.storage.sync.get({ speedKey: d, skipSeconds: 10 }, function(items) { document.getElementById(speedKey).value items.speedKey; document.getElementById(skipSeconds).value items.skipSeconds; }); document.getElementById(saveOptions).addEventListener(click, function() { const speedKey document.getElementById(speedKey).value.toLowerCase(); const skipSeconds parseInt(document.getElementById(skipSeconds).value); if (speedKey.length ! 1 || !/^[a-z]$/.test(speedKey)) { alert(快捷键必须为单个字母); return; } chrome.storage.sync.set({ speedKey: speedKey, skipSeconds: skipSeconds }, function() { const status document.getElementById(optionsStatus); status.textContent 高级设置已保存; status.style.color green; }); }); });7. 加载、测试与调试加载插件打开 Chrome/Edge 浏览器进入chrome://extensions/或edge://extensions/。打开右上角的“开发者模式”。点击“加载已解压的扩展程序”。选择你项目所在的文件夹例如bilibili-enhancer。测试功能访问任何一个B站视频页面如https://www.bilibili.com/video/BV1xx411c7mD。按D键观察播放速度是否变化并看到浮动提示。点击浏览器工具栏上的插件图标弹出窗口应能正常显示和保存设置。检查控制台F12 - Console是否有我们插件打印的日志。调试内容脚本在B站页面按F12打开开发者工具。转到Sources标签页在左侧导航栏中你应该能看到一个名为chrome-extension://[你的插件ID]/content.js的文件可以在这里打断点调试。8. 常见问题与排查思路问题现象可能原因排查方式解决方案插件图标未显示在工具栏未正确声明action或图标路径错误检查manifest.json中action和default_icon配置检查图标文件是否存在且格式正确。确保图标文件在指定路径且格式为PNG。重启浏览器或重新加载插件。快捷键在B站页面无效1. 内容脚本未注入。2. 事件监听被阻止。3. 选择器找不到视频元素。1. 检查manifest.json的matches是否正确。2. 在B站页面控制台输入document.querySelector(video)看是否找到元素。3. 在content.js开头加console.log看是否执行。1. 确保域名匹配。2. B站页面结构可能已更新需调整视频元素选择器。3. 确认插件已重新加载。Popup页面无法保存设置popup.js未正确监听事件或存储权限未声明1. 检查manifest.json是否包含storage权限。2. 在Popup中打开控制台右键-检查查看JS错误。1. 添加storage权限。2. 修复popup.js中的JS语法错误。样式修改未生效CSS选择器优先级不够或B站样式已更新1. 使用开发者工具检查目标元素看注入的CSS是否被覆盖。2. 检查content.css是否被正确注入。1. 使用更具体的选择器或添加!important谨慎。2. 在manifest.json中确认css文件路径正确。与网站更新冲突B站前端代码更新导致选择器失效或API变化观察功能是否在某个时间点后集体失效。这是此类插件最常见的问题。需要定期维护更新选择器和交互逻辑。9. 最佳实践与工程建议开发一个稳定、易维护的浏览器插件需要遵循一些工程原则模块化与配置化将不同功能快捷键、样式、信息增强拆分成独立的模块或函数。将所有可配置项如快捷键键位、开关状态通过chrome.storage管理并在options.html中提供界面。健壮的选择器B站前端结构复杂且可能变动。避免使用过于脆弱的选择器如.class1 .class2 div:nth-child(3)。优先使用相对稳定、语义化的类名或属性并做好回退处理。安全的通信内容脚本与后台脚本之间的消息传递是异步的。确保使用sendResponse回调或Promise正确处理响应。对于涉及网络请求的操作尽量放在后台脚本中。性能考虑内容脚本直接运行在网页中要避免执行耗时的同步操作或频繁的DOM查询例如在scroll事件中直接进行复杂查询。使用MutationObserver时要设置合适的观察选项避免过度触发。版本管理与更新在manifest.json中维护好版本号。如果插件功能发生较大变化考虑通过chrome.runtime.onInstalled事件来迁移旧版用户的设置数据。尊重平台与合规这是最重要的原则。插件功能应止步于“增强体验”明确避免以下行为严禁尝试下载B站付费、充电专属视频。这不仅违反用户协议更可能涉及法律风险。谨慎处理任何涉及用户认证如B站登录态的操作绝对不要尝试窃取或模拟登录。查询用户信息如UID注册时间时必须使用公开、合规的接口并遵守其调用频率限制。在插件描述和功能中明确说明用途避免误导用户。用户隐私如果插件需要收集任何用户数据即使是本地存储的设置应提供清晰的隐私说明。我们的示例插件仅使用chrome.storage.sync来同步用户自己的设置不涉及数据上传。通过这个从零开始的实践你不仅得到了一个可以实际使用的B站增强插件更重要的是掌握了浏览器插件开发的核心模式通过manifest.json声明、通过content_scripts注入逻辑与样式、通过消息传递与后台脚本通信、通过storageAPI 管理状态。这套模式可以迁移到对任何网站的体验增强开发中。你可以基于这个基础框架继续探索更复杂的功能例如更智能的广告识别与处理需极其谨慎、更强大的播放列表管理、甚至是与本地其他工具的联动。记住技术是工具用它来创造提升效率、尊重规则的有趣产品才是最有价值的。