You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:20:29