Chrome扩展开发:识别网页输入匹配数组项并弹出提示
实现Chrome扩展识别网页输入并弹出匹配提示
你的需求需要通过**内容脚本(Content Script)**实现,因为扩展弹窗和目标网页(如Google Docs)属于不同的上下文,内容脚本可以注入到网页中访问DOM并监听输入行为。以下是具体实现步骤:
1. 修改Manifest.json配置
需要添加内容脚本的注入规则,以及对应的主机权限,让扩展能访问目标网页:
{ "name": "Extension", "description": "Extension Description", "version": "1.0", "manifest_version": 2, "permissions": [ "activeTab", "https://docs.google.com/*" // 添加Google Docs的访问权限 ], "browser_action": { "default_popup": "index.html", "default_title": "Open the popup" }, "icons": { "16": "logo192.png", "48": "logo192.png", "128": "logo192.png" }, "content_scripts": [ { "matches": ["https://docs.google.com/*"], // 匹配Google Docs页面 "js": ["content.js"], // 要注入的内容脚本 "run_at": "document_idle" // 页面加载完成后注入 } ] }
2. 编写内容脚本content.js
这个脚本会注入到目标网页中,监听输入事件并检测匹配项:
// 共享你的匹配数组(也可以通过chrome.storage.sync共享,方便后续修改) const internalLinks = [ { name: 'Sam', link: 'somewhere@gmail.com' }, { name: 'Ash', link: 'something@gmail.com' } ]; // 监听页面的输入事件 document.addEventListener('input', (e) => { const target = e.target; // 只处理可编辑区域(Google Docs的编辑区标签通常是div[contenteditable="true"]) if (target.isContentEditable || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { // 获取当前输入的文本(这里取光标附近的单词,更精准) const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const wordRange = expandToWord(range); const inputWord = wordRange.toString().trim(); // 检查是否匹配数组中的项 const matchedItem = internalLinks.find(item => item.name === inputWord); if (matchedItem) { // 弹出提示(这里用自定义浮动弹窗,也可以用chrome.notifications.create) showMatchNotification(matchedItem, target); } } } }); // 辅助函数:把选中范围扩展到整个单词 function expandToWord(range) { const startNode = range.startContainer; const startOffset = range.startOffset; const endNode = range.endContainer; const endOffset = range.endOffset; // 向左扩展到单词开头 while (startOffset > 0 && /\w/.test(startNode.textContent[startOffset - 1])) { range.setStart(startNode, startOffset - 1); } // 向右扩展到单词结尾 while (endOffset < endNode.textContent.length && /\w/.test(endNode.textContent[endOffset])) { range.setEnd(endNode, endOffset + 1); } return range; } // 自定义匹配提示弹窗 function showMatchNotification(item, anchorElement) { // 先移除已存在的提示 const existingToast = document.getElementById('extension-match-toast'); if (existingToast) existingToast.remove(); // 创建提示元素 const toast = document.createElement('div'); toast.id = 'extension-match-toast'; toast.style.cssText = ` position: absolute; background: #2d3748; color: white; padding: 8px 12px; border-radius: 4px; font-size: 14px; z-index: 999999; box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; toast.textContent = `匹配到联系人:${item.name} - ${item.link}`; // 定位到输入元素附近 const rect = anchorElement.getBoundingClientRect(); toast.style.top = `${rect.top + window.scrollY - 40}px`; toast.style.left = `${rect.left + window.scrollX}px`; document.body.appendChild(toast); // 3秒后自动消失 setTimeout(() => { toast.remove(); }, 3000); }
3. 共享匹配数据(可选优化)
如果需要在弹窗和内容脚本之间共享internalLinks数组,避免重复定义,可以用Chrome的存储API:
在App.js中保存数据到存储:
import { useEffect, useState } from 'react'; import './App.css'; import { Grid, Stack, TextField, Button,Container } from '@mui/material'; function App() { const [content, setContent] = useState('') const [internalLinks, setInternalLinks] = useState([ { name: 'Sam', link: 'somewhere@gmail.com' }, { name: 'Ash', link: 'something@gmail.com' } ]) // 页面加载时把数据同步到存储 useEffect(() => { chrome.storage.sync.set({ internalLinks }, () => { console.log('匹配数据已同步'); }); }, [internalLinks]); const generateContent = (content) => { const final = internalLinks.reduce((a, b) => { return a.replaceAll(b.name, `<a href=${b.link}>${b.name}</a>`); }, content); return final; }; return ( <Container > <Stack direction="column" alignItems="center" justifyContent="space-between" mb={5}> </Stack> <Stack direction="row" alignItems="start" justifyContent="start" mb={5}> <TextField name="Content" placeholder="Content" multiline={true} value={content} rows={18} sx={{width: '100%'}} onChange={(e) => setContent(e.target.value)} /> <div style={{maxWidth:"50%", paddingLeft:"10px"}} dangerouslySetInnerHTML={{__html: generateContent(content)}}></div> </Stack> </Container> ); } export default App;
在content.js中读取存储的数据:
// 从存储读取匹配数组 let internalLinks = []; chrome.storage.sync.get('internalLinks', (result) => { if (result.internalLinks) { internalLinks = result.internalLinks; } }); // 后续监听逻辑不变...
注意事项
- Google Docs的DOM结构比较特殊,编辑区是动态生成的
contenteditable元素,上述代码已经针对这种情况做了适配,但如果后续Google Docs更新DOM,可能需要调整选择器。 - 如果你需要支持更多网站,只需要在Manifest的
content_scripts.matches中添加对应的域名规则即可。
内容的提问来源于stack exchange,提问作者Tom Galland
相关产品推荐
相关产品推荐

