2024 / 9 / 6
前提知识:油猴脚本的用法
按住 Alt 同时用鼠标选中文字,从左到右选中后的效果是高亮显示,而从右到左选中后的效果是隐藏内容,点击后才显示。前者可以用于第一遍快速浏览,后者可以用于主动回忆。
即使是网页刷新后,痕迹依然得以保留。
// ==UserScript== // @name Alt+Select Highlight with Direction // @namespace http://tampermonkey.net/ // @version 1.2 // @description 按住 Alt 键时根据选择方向不同应用不同的高亮效果,并在刷新后保留 // @author xxhk.org // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; let altPressed = false; const HIGHLIGHT_CLASS = 'highlighted-text'; const STORAGE_KEY = 'highlightedText'; // 监听 Alt 键的按下与释放 document.addEventListener('keydown', (e) => { if (e.key === 'Alt') { altPressed = true; } }); document.addEventListener('keyup', (e) => { if (e.key === 'Alt') { altPressed = false; } }); // 加载存储的高亮内容 function loadHighlights() { const storedData = localStorage.getItem(STORAGE_KEY); if (storedData) { const highlights = JSON.parse(storedData); highlights.forEach(({ text, path, hidden }) => { const element = document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (element) { const range = document.createRange(); const textNode = element.childNodes[0]; // 假设是第一个子节点 const startIndex = textNode.textContent.indexOf(text); if (startIndex !== -1) { range.setStart(textNode, startIndex); range.setEnd(textNode, startIndex + text.length); highlightRange(range, hidden); } } }); } } // 监听鼠标松开事件 document.addEventListener('mouseup', () => { if (altPressed) { const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const isBackward = selection.anchorOffset > selection.focusOffset; highlightRange(range, isBackward); saveHighlight(range, isBackward); } // 清除选中状态 selection.removeAllRanges(); } }); // 高亮选中的范围 function highlightRange(range, isHidden) { const span = document.createElement('span'); span.style.backgroundColor = 'yellow'; // 设置高亮背景色 span.style.padding = '2px 5px'; // 设置 padding span.style.margin = '0 2px'; // 设置 margin span.style.borderRadius = '5px'; // 设置圆角 span.style.display = 'inline-block'; // 保证 padding 和 margin 有效 if (isHidden) { // 如果是从右到左选中,设置文字颜色和背景色一致 span.style.color = 'yellow'; // 添加事件,当点击时恢复文字颜色 span.addEventListener('click', () => { span.style.color = 'black'; // 恢复文字颜色 }); } else { // 正常选中情况 span.style.color = 'black'; // 默认黑色文字 } span.className = HIGHLIGHT_CLASS; range.surroundContents(span); } // 保存高亮内容到 localStorage function saveHighlight(range, isHidden) { const text = range.toString(); const path = getXPath(range.startContainer); let highlights = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); highlights.push({ text, path, hidden: isHidden }); localStorage.setItem(STORAGE_KEY, JSON.stringify(highlights)); } // 生成 XPath 表示范围内的文本节点 function getXPath(node) { if (node.nodeType === Node.TEXT_NODE) { node = node.parentNode; } if (node.id) { return `//*[@id="${node.id}"]`; } let path = ''; while (node && node.nodeType === Node.ELEMENT_NODE) { let index = 1; let sibling = node.previousSibling; while (sibling) { if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName === node.nodeName) { index++; } sibling = sibling.previousSibling; } path = `/${node.nodeName.toLowerCase()}[${index}]` + path; node = node.parentNode; } return path; } // 页面加载时恢复高亮 window.addEventListener('load', loadHighlights); })();