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

求助开发Chrome高亮扩展:实现选中文本持久高亮功能

Hey there! Let's build that persistent text highlighter extension you're aiming for. The key challenge here is moving beyond the temporary ::selection highlight—we need to capture selected text ranges, store them persistently, and render them permanently even after clicking away or reloading the page. Let's fix your existing code and add the core functionality step by step.

First, Fix Existing Code Issues

Let's start by cleaning up the bugs in your current files:

1. options.js - Fix Element Selection & Improve UI

Your code uses an invalid document.get('buttonDiv') method. Replace it with document.getElementById, plus add some basic styling for color buttons:

'use strict';

let page = document.getElementById('buttonDiv');
const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];

function constructOptions(kButtonColors) {
  for (let item of kButtonColors) {
    let button = document.createElement('button');
    button.style.backgroundColor = item;
    button.style.width = '40px';
    button.style.height = '40px';
    button.style.border = 'none';
    button.style.borderRadius = '50%';
    button.style.margin = '0 5px';
    button.style.cursor = 'pointer';

    button.addEventListener('click', function() {
      chrome.storage.sync.set({color: item}, function() {
        console.log('Highlight color set to ' + item);
      });
    });

    page.appendChild(button); // Use the pre-defined page variable
  }
}

constructOptions(kButtonColors);

2. background.js - Show Page Action on All Pages

Your current rule only shows the page action on developer.chrome.com. Let's adjust it to work on all HTTP/HTTPS pages:

'use strict';

chrome.runtime.onInstalled.addListener(function() {
  chrome.storage.sync.set({color: '#3aa757'}, function() {
    console.log('Default highlight color set to green (#3aa757)');
  });

  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [
        new chrome.declarativeContent.PageStateMatcher({
          pageUrl: {schemes: ['http', 'https']},
        })
      ],
      actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });
});

Core Functionality: Persistent Highlighting

The magic happens in the content script (script.js). We'll add logic to:

  • Capture text selections when the user releases the mouse
  • Wrap selected text in a highlighted span
  • Store highlight data so it persists across page reloads
  • Restore highlights when the page loads

script.js - Content Script

'use strict';

// Load saved highlights when the page loads
document.addEventListener('DOMContentLoaded', restoreHighlights);

// Listen for mouseup to detect text selection
document.addEventListener('mouseup', handleTextSelection);

// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'clearHighlights') {
    const highlights = document.querySelectorAll('.persistent-highlight');
    highlights.forEach(highlight => {
      // Replace the span with its contents
      const parent = highlight.parentNode;
      parent.replaceChild(highlight.firstChild, highlight);
      parent.normalize(); // Merge adjacent text nodes
    });
    sendResponse({status: 'success'});
  }
});

// Handle text selection and create highlight
function handleTextSelection() {
  const selection = window.getSelection();
  if (!selection || selection.isCollapsed) return;

  // Get the selected range
  const range = selection.getRangeAt(0);
  const selectedText = range.toString().trim();
  if (!selectedText) return;

  // Get the saved highlight color from storage
  chrome.storage.sync.get('color', (data) => {
    const highlightColor = data.color || '#3aa757';
    createHighlight(range, highlightColor);
    saveHighlight(range, highlightColor);
    // Clear the native selection
    selection.removeAllRanges();
  });
}

// Create a highlight span and insert it into the DOM
function createHighlight(range, color) {
  const highlightSpan = document.createElement('span');
  highlightSpan.style.backgroundColor = color;
  highlightSpan.style.color = getContrastColor(color); // Ensure text readability
  highlightSpan.classList.add('persistent-highlight');

  // Surround the selected range with the span (handle cross-element ranges)
  try {
    range.surroundContents(highlightSpan);
  } catch (e) {
    const fragment = range.extractContents();
    highlightSpan.appendChild(fragment);
    range.insertNode(highlightSpan);
  }
}

// Save highlight data to chrome storage
function saveHighlight(range, color) {
  // Convert range to serializable data using XPath for node location
  const highlightData = {
    startXPath: getXPath(range.startContainer),
    startOffset: range.startOffset,
    endXPath: getXPath(range.endContainer),
    endOffset: range.endOffset,
    color: color,
    pageUrl: window.location.href
  };

  // Get existing highlights and add the new one
  chrome.storage.sync.get('highlights', (data) => {
    const highlights = data.highlights || [];
    highlights.push(highlightData);
    chrome.storage.sync.set({highlights: highlights});
  });
}

// Restore highlights from storage when page loads
function restoreHighlights() {
  chrome.storage.sync.get('highlights', (data) => {
    const highlights = data.highlights || [];
    // Filter highlights for the current page
    const pageHighlights = highlights.filter(h => h.pageUrl === window.location.href);

    pageHighlights.forEach(highlight => {
      try {
        const startNode = getNodeFromXPath(highlight.startXPath);
        const endNode = getNodeFromXPath(highlight.endXPath);
        if (!startNode || !endNode) return;

        const range = document.createRange();
        range.setStart(startNode, highlight.startOffset);
        range.setEnd(endNode, highlight.endOffset);

        createHighlight(range, highlight.color);
      } catch (e) {
        console.error('Failed to restore highlight:', e);
      }
    });
  });
}

// Helper: Get XPath for a DOM node
function getXPath(node) {
  if (node.id) return `//*[@id="${node.id}"]`;
  if (node === document.body) return '/html/body';

  let count = 0;
  let sibling = node.previousSibling;
  while (sibling) {
    if (sibling.nodeType === 1 && sibling.tagName === node.tagName) count++;
    sibling = sibling.previousSibling;
  }

  return `${getXPath(node.parentNode)}/${node.tagName.toLowerCase()}[${count + 1}]`;
}

// Helper: Get DOM node from XPath
function getNodeFromXPath(xpath) {
  return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

// Helper: Get contrasting text color for readability
function getContrastColor(hexColor) {
  hexColor = hexColor.replace('#', '');
  const r = parseInt(hexColor.substr(0, 2), 16);
  const g = parseInt(hexColor.substr(2, 2), 16);
  const b = parseInt(hexColor.substr(4, 2), 16);
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  return luminance > 0.5 ? '#000000' : '#ffffff';
}

style.css - Content Script CSS

Add this to ensure highlights don't interfere with default styles:

.persistent-highlight {
  padding: 2px 0;
  border-radius: 3px;
  user-select: none; /* Prevent re-selecting the highlighted text */
}

Add a Clear Highlights Button to Popup

Update your popup.html and popup.js to let users clear highlights on the current page:

popup.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 150px; padding: 10px; }
    button { width: 100%; padding: 8px; margin: 5px 0; cursor: pointer; border: none; border-radius: 4px; background: #4688f1; color: white; }
  </style>
</head>
<body>
  <button id="clearHighlights">Clear All Highlights</button>
  <script src="popup.js"></script>
</body>
</html>

popup.js

'use strict';

document.getElementById('clearHighlights').addEventListener('click', () => {
  // Send message to content script to clear highlights
  chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, {action: 'clearHighlights'}, () => {
      // Remove highlights from storage for this page
      chrome.storage.sync.get('highlights', (data) => {
        const highlights = data.highlights || [];
        const updatedHighlights = highlights.filter(h => h.pageUrl !== tabs[0].url);
        chrome.storage.sync.set({highlights: updatedHighlights});
        window.close();
      });
    });
  });
});

Final Notes

  • Manifest Update: Ensure your manifest.json content script matches all URLs by updating matches to ["<all_urls>"].
  • Dynamic Pages: For single-page apps (SPAs), add a MutationObserver to detect DOM updates and re-apply highlights, since XPath references might break when the DOM changes.
  • Performance: If you expect many highlights, consider adding pagination or a "clear all highlights across pages" option to avoid storage bloat.

内容的提问来源于stack exchange,提问作者rrrrrauno

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:01:13