从零开发一款 Chrome 浏览器插件,手把手教你做出第一个“网页笔记高亮“工具
本文适合有 HTML/CSS/JS 基础的同学,手把手带你从创建项目到发布,一条龙掌握 Chrome 插件开发。
📌 一、Chrome 插件是什么?
Chrome 插件(也叫扩展程序)是用 HTML、CSS、JavaScript 编写的 Web 小工具,运行在 Chrome 浏览器中,可以读取和修改网页内容、自定义浏览器 UI、与服务器通信等。
常见应用场景:
- 🖍️ 网页批注/高亮笔记(今天我们要做的)
- 🌐 广告拦截
- 📊 价格监控
- 🔍 SEO 分析工具
- 📂 标签页管理
- 🤖 AI 助手侧边栏
📌 二、项目结构
先来看看插件的目录结构,非常清晰:
my-extension/ ← 项目根目录
├── manifest.json ← 插件配置文件(必填)
├── popup.html ← 点击图标弹出的页面
├── popup.js ← popup 逻辑
├── popup.css ← popup 样式
├── content.js ← 内容脚本(注入到网页中运行)
├── background.js ← 后台脚本(独立于网页运行)
├── icons/ ← 图标资源
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── options.html ← 可选:设置页面
核心原则:content script 运行在网页环境中,能访问 DOM;popup 和 background 运行在 Chrome 扩展环境中,彼此通过消息机制通信。
📌 三、manifest.json —— 插件的"身份证"
这是所有 Chrome 插件的入口文件,Chrome 根据它来了解插件的名称、权限、入口文件等信息。
创建 manifest.json:
{
"manifest_version": 3,
"name": "网页笔记高亮工具",
"version": "1.0.0",
"description": "选中网页文字即可高亮标注,并保存笔记到本地",
"permissions": [
"storage",
"activeTab"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
manifest_version 说明
| 版本 | 说明 |
|---|---|
| Manifest V2 | 旧版,2023 年前主流,2024 年起 Chrome Web Store 已停止接收新 V2 插件 |
| Manifest V3 | 当前主流,安全性更高,使用 service_worker 替代后台页面,性能更好 |
💡 强烈建议使用 Manifest V3,Google 已明确推进 V3 全面替代 V2。
📌 四、内容脚本 content.js —— "入侵"网页
内容脚本运行在网页的上下文中,可以访问和修改网页的 DOM 元素。我们用它来实现:
- 监听鼠标选中文本事件
- 渲染高亮样式
- 弹出笔记输入框
创建 content.js:
// content.js
// 监听鼠标选择文字事件
document.addEventListener('mouseup', (e) => {
const selection = window.getSelection();
const selectedText = selection.toString().trim();
// 选中文本超过3个字符才触发
if (selectedText.length > 3) {
showNotePopup(selection, selectedText);
}
});
// 显示笔记弹窗
function showNotePopup(selection, text) {
// 移除已有的弹窗
const existing = document.getElementById('highlight-note-popup');
if (existing) existing.remove();
// 创建弹窗
const popup = document.createElement('div');
popup.id = 'highlight-note-popup';
popup.innerHTML = `
<div class="popup-inner">
<p class="popup-title">📝 为「${text.substring(0, 20)}${text.length > 20 ? '...' : ''}」添加笔记</p>
<textarea id="note-input" placeholder="写下你的笔记..." rows="3"></textarea>
<div class="popup-buttons">
<button id="btn-save">💾 保存</button>
<button id="btn-cancel">✖ 取消</button>
</div>
</div>
`;
document.body.appendChild(popup);
// 定位弹窗(出现在鼠标附近)
popup.style.top = `${e.clientY + 10}px`;
popup.style.left = `${e.clientX + 10}px`;
// 保存按钮
document.getElementById('btn-save').addEventListener('click', () => {
const note = document.getElementById('note-input').value;
saveHighlight(text, note);
popup.remove();
});
// 取消按钮
document.getElementById('btn-cancel').addEventListener('click', () => {
popup.remove();
});
// 点击其他地方自动关闭
setTimeout(() => {
document.addEventListener('click', function closePopup(e) {
if (!popup.contains(e.target)) {
popup.remove();
document.removeEventListener('click', closePopup);
}
});
}, 100);
}
// 保存高亮到 Chrome Storage
function saveHighlight(text, note) {
const highlight = {
id: Date.now(),
text: text,
note: note,
url: window.location.href,
pageTitle: document.title,
createdAt: new Date().toLocaleString('zh-CN')
};
chrome.storage.local.get(['highlights'], (result) => {
const highlights = result.highlights || [];
highlights.push(highlight);
chrome.storage.local.set({ highlights });
// 同时给选中文本加高亮样式
const range = window.getSelection().getRangeAt(0);
const span = document.createElement('span');
span.className = 'my-highlight';
span.title = note;
span.dataset.id = highlight.id;
range.surroundContents(span);
});
}
// 恢复页面上的已有高亮
chrome.storage.local.get(['highlights'], (result) => {
const highlights = result.highlights || [];
highlights.forEach(h => {
if (h.url === window.location.href) {
highlightExistingText(h.text, h.id, h.note);
}
});
});
function highlightExistingText(text, id, note) {
// 简单实现:在页面中搜索对应文本并高亮
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.textContent.includes(text)) {
const span = document.createElement('span');
span.className = 'my-highlight';
span.title = note;
span.dataset.id = id;
span.textContent = text;
node.parentNode.replaceChild(span, node);
break;
}
}
}
创建 content.css 高亮样式:
/* content.css */
.my-highlight {
background-color: #ffe066 !important;
border-radius: 3px;
padding: 0 2px;
cursor: pointer;
position: relative;
}
.my-highlight:hover {
background-color: #ffd700 !important;
}
#highlight-note-popup {
position: fixed;
z-index: 2147483647;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.popup-inner {
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
padding: 14px;
width: 280px;
animation: fadeInUp 0.2s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.popup-title {
margin: 0 0 10px;
font-size: 13px;
color: #333;
word-break: break-all;
}
#note-input {
width: 100%;
box-sizing: border-box;
padding: 8px;
border: 1px solid #d0d0d0;
border-radius: 8px;
font-size: 13px;
resize: none;
outline: none;
font-family: inherit;
}
#note-input:focus {
border-color: #ffca28;
}
.popup-buttons {
display: flex;
gap: 8px;
margin-top: 10px;
}
.popup-buttons button {
flex: 1;
padding: 7px 0;
border: none;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
#btn-save {
background: #ffca28;
color: #333;
}
#btn-save:hover {
background: #ffd700;
}
#btn-cancel {
background: #f0f0f0;
color: #666;
}
#btn-cancel:hover {
background: #e0e0e0;
}
📌 五、popup 弹窗 —— 插件主界面
popup 是点击 Chrome 工具栏插件图标后弹出的页面,通常用于查看和管理插件数据。
创建 popup.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="popup-container">
<header class="popup-header">
<h1>📒 我的笔记</h1>
<button id="btn-clear">🗑️ 清空</button>
</header>
<div id="notes-list" class="notes-list">
<p class="empty-tip">还没有笔记,试试在网页上选中文字添加~</p>
</div>
<footer class="popup-footer">
<span id="count">共 0 条笔记</span>
<a href="#" id="btn-open" target="_blank">打开笔记页面</a>
</footer>
</div>
<script src="popup.js"></script>
</body>
</html>
创建 popup.css:
/* popup.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 360px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f8f9fa;
}
.popup-container {
padding: 16px;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.popup-header h1 {
font-size: 16px;
color: #1a1a1a;
}
#btn-clear {
background: none;
border: none;
font-size: 12px;
color: #999;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
}
#btn-clear:hover {
background: #f0f0f0;
color: #d32f2f;
}
.notes-list {
max-height: 400px;
overflow-y: auto;
}
.note-item {
background: #fff;
border-radius: 10px;
padding: 12px;
margin-bottom: 10px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
cursor: pointer;
transition: box-shadow 0.2s;
}
.note-item:hover {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.note-text {
font-size: 13px;
color: #333;
margin-bottom: 6px;
line-height: 1.5;
}
.note-note {
font-size: 12px;
color: #666;
background: #fffbe6;
border-left: 3px solid #ffca28;
padding: 6px 8px;
border-radius: 0 6px 6px 0;
margin-bottom: 6px;
}
.note-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.note-page {
font-size: 11px;
color: #aaa;
text-decoration: none;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.note-time {
font-size: 11px;
color: #bbb;
}
.empty-tip {
text-align: center;
color: #bbb;
font-size: 13px;
padding: 30px 0;
}
.popup-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid #eee;
margin-top: 4px;
}
.popup-footer span {
font-size: 12px;
color: #aaa;
}
.popup-footer a {
font-size: 12px;
color: #ffca28;
text-decoration: none;
font-weight: 500;
}
.popup-footer a:hover {
text-decoration: underline;
}
创建 popup.js:
// popup.js
const notesList = document.getElementById('notes-list');
const countLabel = document.getElementById('count');
const btnClear = document.getElementById('btn-clear');
function renderNotes() {
chrome.storage.local.get(['highlights'], (result) => {
const highlights = result.highlights || [];
countLabel.textContent = `共 ${highlights.length} 条笔记`;
if (highlights.length === 0) {
notesList.innerHTML = '<p class="empty-tip">还没有笔记,试试在网页上选中文字添加~</p>';
return;
}
// 按时间倒序
highlights.slice().reverse().forEach(h => {
const item = document.createElement('div');
item.className = 'note-item';
item.innerHTML = `
<div class="note-text">📌 ${escapeHtml(h.text)}</div>
${h.note ? `<div class="note-note">💬 ${escapeHtml(h.note)}</div>` : ''}
<div class="note-meta">
<a href="${escapeHtml(h.url)}" class="note-page" target="_blank" title="${escapeHtml(h.pageTitle)}">🌐 ${escapeHtml(h.pageTitle)}</a>
<span class="note-time">${h.createdAt}</span>
</div>
`;
item.addEventListener('click', () => {
// 点击跳转到对应页面
chrome.tabs.create({ url: h.url });
});
notesList.appendChild(item);
});
});
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// 清空
btnClear.addEventListener('click', () => {
if (confirm('确定清空所有笔记吗?')) {
chrome.storage.local.set({ highlights: [] });
renderNotes();
}
});
renderNotes();
📌 六、后台脚本 background.js
Manifest V3 中,后台脚本是 Service Worker,关即即停,不需要页面常驻。它主要负责:
- 监听插件安装/更新事件
- 跨页面消息通信
- 定时任务等
创建 background.js:
// background.js
// 插件安装时提示
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
console.log('🎉 插件安装成功!');
// 初始化存储
chrome.storage.local.set({ highlights: [] });
}
if (details.reason === 'update') {
console.log('🔄 插件已更新到新版本');
}
});
// 监听来自 content script 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getNotes') {
chrome.storage.local.get(['highlights'], (result) => {
sendResponse({ data: result.highlights || [] });
});
return true; // 异步响应需要返回 true
}
});
📌 七、创建图标
图标放在 icons/ 目录下,需要 3 个尺寸:
| 文件名 | 尺寸 | 用途 |
|---|---|---|
icon16.png |
16×16 | 地址栏小图标 |
icon48.png |
48×48 | 扩展管理页面 |
icon128.png |
128×128 | Chrome Web Store |
💡 如果没有设计工具,可以去 Flaticon 或 IconHub 下载免费图标,或用在线工具生成:
https://favicon.io/
📌 八、加载并测试插件
- 打开 Chrome,地址栏输入:
chrome://extensions/ - 右上角打开「开发者模式」
- 点击「加载已解压的扩展程序」
- 选择你的
my-extension/文件夹 - 插件图标会出现在 Chrome 工具栏
⚠️ 如果工具栏没有图标,点击拼图 🧩 图标 → 固定你的插件
📌 九、发布到 Chrome 商店(可选)
- 打包插件:在
chrome://extensions/页面点击「打包扩展程序」 - 注册 Chrome Web Store 开发者账号($5 一次性注册费)
- 登录 Chrome Web Store Developer Dashboard
- 上传
.zip包,填写插件描述、截图、隐私政策等 - 提交审核(通常 1-3 个工作日)
📌 十、完整项目目录一览
my-extension/
├── manifest.json ✅
├── popup.html ✅
├── popup.css ✅
├── popup.js ✅
├── content.js ✅
├── content.css ✅
├── background.js ✅
└── icons/
├── icon16.png
├── icon48.png
└── icon128.png
✅ 总结
通过这个"网页笔记高亮工具",我们学会了:
| 技术点 | 说明 |
|---|---|
manifest.json |
插件配置,注册权限、入口文件 |
| Content Script | 注入网页,操作 DOM |
| Popup 页面 | 工具栏弹窗界面 |
| Background Worker | 后台任务处理 |
| Chrome Storage API | 数据持久化存储 |
| Service Worker | Manifest V3 的后台机制 |
Chrome 插件本质上是运行在浏览器中的 Web 应用,门槛极低、变现能力强。学会这套技能后,你可以:
- 🔧 做内部效率工具(团队共享)
- 💰 开发商业插件变现
- 📦 打造自己的产品矩阵
快动手做一个属于你的 Chrome 插件吧!有任何问题欢迎在评论区交流 👇
如果你觉得这篇文章有帮助,欢迎点赞、收藏!
更多推荐



所有评论(0)