粗体文本较新版本的 Edge 砍掉了 Web 选择,可在快捷键方式添加启动参数找回
--enable-features=msEdgeAreaSelect
Windows 在这有个小坑: 将带启动参数或便携式浏览器设置为默认浏览器
但有的浏览器似乎不起作用
或者是手动修改注册表,也试过,有点麻烦,效果也不理想
如果有更好的方法也请指教
平替脚本(非 Edge 浏览器的选择)
1. Web选择
不太习惯这个脚本的触发方式,只试用了一下
2. 框选复制 beta
瞎捣鼓出来的,要求不高的可以试试,简单分享,算不上推荐
代码(点击查看):
// ==UserScript==
// @name 框选复制文本 beta
// @namespace http://tampermonkey.net/
// @version 0.5
// @description 修饰键 + 左键拖拽框选松开后自动复制框选文本
// @author You
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let selectionBox = null;
let isSelecting = false;
let startX, startY;
let highlightedElements = [];
let splitMode = false;
let mask = null;
const isMac = /Mac/.test(navigator.userAgent);
const modKey = isMac ? 'metaKey' : 'ctrlKey';
// [modKey]:Mac 为 metaKey ,Windows 为 ctrlKey
// 非拆分模式修饰键
const nonSplitModeShortcut = { alt: true, [ modKey ]: false, shift: false };
// 拆分模式修饰键
const splitModeShortcut = { alt: false, [ modKey ]: false, shift: false };
// 是否屏蔽修饰键 + 左键的默认行为,true 为开启,false 为关闭
let isMask = false;
function createSelectionBox(x, y, splitMode) {
const box = document.createElement('div');
box.style.border = splitMode ? '3px dotted #62a262' : '3px dashed #62a262';
box.style.position = 'absolute';
box.style.zIndex = '2147483647';
box.style.pointerEvents = 'none';
box.style.left = `${ x }px`;
box.style.top = `${ y }px`;
return box;
}
function updateSelectionBox(box, startX, startY, currentX, currentY) {
const minX = Math.min(startX, currentX);
const minY = Math.min(startY, currentY);
const width = Math.abs(startX - currentX);
const height = Math.abs(startY - currentY);
box.style.left = `${ minX }px`;
box.style.top = `${ minY }px`;
box.style.width = `${ width }px`;
box.style.height = `${ height }px`;
}
function clearHighlightedElements() {
highlightedElements.forEach(el => {
el.style.outline = '';
el.style.backgroundColor = '';
});
highlightedElements = [];
}
function highlightElement(el) {
el.style.outline = '2px solid rgb(135, 206, 250)';
el.style.backgroundColor = 'rgba(135, 206, 250, 0.3)';
highlightedElements.push(el);
}
function elementDirectlyContainsText(el, splitMode) {
if (!splitMode) {
return Array.from(el.childNodes).some(node =>
node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '');
} else {
let textNodes = Array.from(el.childNodes).filter(node =>
node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '');
textNodes.forEach(textNode => {
let textSegments = textNode.textContent.split(/<br\s*\/?>|\n/);
textSegments.forEach(segment => {
if (!segment.trim()) return;
let range = document.createRange();
let startIndex = textNode.textContent.indexOf(segment);
range.setStart(textNode, startIndex);
range.setEnd(textNode, startIndex + segment.length);
let span = document.createElement('span');
span.style.outline = '2px solid rgb(135, 206, 250)';
span.style.backgroundColor = 'rgba(135, 206, 250, 0.3)';
span.setAttribute('data-split-text', 'true');
range.surroundContents(span);
highlightedElements.push(span);
});
});
return textNodes.length > 0;
}
}
function isRectOverlap(rect1, rect2) {
return !(rect1.right < rect2.left || rect1.left > rect2.right ||
rect1.bottom < rect2.top || rect1.top > rect2.bottom);
}
function highlightElementsInRect(rect, splitMode) {
if (!splitMode) {
clearHighlightedElements();
const elements = document.querySelectorAll('*:not(html):not(body):not(script)');
let possibleElements = [];
elements.forEach(el => {
const elRect = el.getBoundingClientRect();
if (isRectOverlap(rect, elRect) && elementDirectlyContainsText(el, splitMode)) {
possibleElements.push(el);
}
});
possibleElements.forEach(el => {
let parent = el.parentElement;
let isChildOfHighlighted = false;
while (parent) {
if (possibleElements.includes(parent)) {
isChildOfHighlighted = true;
break;
}
parent = parent.parentElement;
}
if (!isChildOfHighlighted) {
highlightElement(el);
}
});
} else {
clearHighlightedElements();
let elements = document.querySelectorAll('*:not(html):not(body):not(script)');
elements.forEach(el => {
let elRect = el.getBoundingClientRect();
if (isRectOverlap(rect, elRect)) {
elementDirectlyContainsTextSplitMode(el);
}
});
}
}
function elementDirectlyContainsTextSplitMode(el) {
const textNodes = Array.from(el.childNodes).filter(node =>
node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '');
textNodes.forEach(textNode => {
const textSegments = textNode.textContent.split(/<br\s*\/?>|\n/);
textSegments.forEach(segment => {
if (!segment.trim()) return;
const range = document.createRange();
const startIndex = textNode.textContent.indexOf(segment);
range.setStart(textNode, startIndex);
range.setEnd(textNode, startIndex + segment.length);
const wrapper = document.createElement('span');
wrapper.textContent = segment;
wrapper.style.outline = '2px solid rgb(135, 206, 250)';
wrapper.style.backgroundColor = 'rgba(135, 206, 250, 0.3)';
wrapper.setAttribute('data-split-text', 'true');
range.deleteContents();
range.insertNode(wrapper);
highlightedElements.push(wrapper);
});
});
return textNodes.length > 0;
}
function removeElementsWithDuplicatedText(elements) {
const uniqueTextContents = new Map();
elements.forEach(el => {
const textContent = el.innerText.replace(/\s+/g, '');
if (uniqueTextContents.has(textContent)) {
uniqueTextContents.get(textContent).push(el);
} else {
uniqueTextContents.set(textContent, [ el ]);
}
});
return [].concat(...Array.from(uniqueTextContents.values()));
}
function copyTextOfHighlightedElements(splitMode) {
let textContents;
if (!splitMode) {
textContents = highlightedElements.map(el => el.innerText.trim() + '\n').join('');
textContents = textContents.replace(/^\s*[\r\n]/gm, '');
} else {
const uniqueHighlightedElements = removeElementsWithDuplicatedText(highlightedElements);
textContents = uniqueHighlightedElements.map(el => el.innerText.trim()).join('\n');
textContents = textContents.replace(/^\s*[\r\n]/gm, '');
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
navigator.clipboard.writeText(textContents).then(() => {
// console.log('复制成功');
}).catch(err => {
console.error('复制失败: ', err);
fallbackCopyTextToClipboard(textContents);
});
} else {
fallbackCopyTextToClipboard(textContents);
}
if (splitMode) {
highlightedElements.forEach(el => {
if (el.getAttribute('data-split-text') === 'true') {
el.outerHTML = el.textContent;
}
});
highlightedElements = [];
}
}
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
const msg = successful ? '复制成功' : '复制失败';
// console.log(msg);
} catch (err) {
console.error('复制失败: ', err);
}
document.body.removeChild(textArea);
}
function maskMouseUpEvent() {
mask = document.createElement('div');
mask.style.position = 'fixed';
mask.style.top = '0';
mask.style.left = '0';
mask.style.width = '100vw';
mask.style.height = '100vh';
mask.style.zIndex = '999999';
mask.style.pointerEvents = 'auto';
mask.style.background = 'rgba(0,0,0,0)';
document.body.appendChild(mask);
mask.addEventListener('mouseup', function (maskMouseUpEvent) {
if (isSelecting) {
isSelecting = false;
const selectionRect = selectionBox.getBoundingClientRect();
highlightElementsInRect(selectionRect, splitMode);
copyTextOfHighlightedElements(splitMode);
clearHighlightedElements();
document.body.removeChild(selectionBox);
selectionBox = null;
document.body.removeChild(mask);
mask = null;
maskMouseUpEvent.preventDefault();
maskMouseUpEvent.stopPropagation();
}
});
}
document.addEventListener('mousedown', function (e) {
if ((nonSplitModeShortcut.alt ? e.altKey : !e.altKey) &&
(nonSplitModeShortcut[ modKey ] ? e[ modKey ] : !e[ modKey ]) &&
(nonSplitModeShortcut.shift ? e.shiftKey : !e.shiftKey) &&
e.button === 0 &&
(nonSplitModeShortcut.alt || nonSplitModeShortcut[ modKey ] || nonSplitModeShortcut.shift)) {
e.preventDefault();
isSelecting = true;
startX = e.pageX;
startY = e.pageY;
selectionBox = createSelectionBox(startX, startY, false);
document.body.appendChild(selectionBox);
splitMode = false;
if (isMask) {
maskMouseUpEvent()
}
} else if ((splitModeShortcut.alt ? e.altKey : !e.altKey) &&
(splitModeShortcut[ modKey ] ? e[ modKey ] : !e[ modKey ]) &&
(splitModeShortcut.shift ? e.shiftKey : !e.shiftKey) &&
e.button === 0 &&
(splitModeShortcut.alt || splitModeShortcut[ modKey ] || splitModeShortcut.shift)) {
e.preventDefault();
isSelecting = true;
startX = e.pageX;
startY = e.pageY;
selectionBox = createSelectionBox(startX, startY, true);
document.body.appendChild(selectionBox);
splitMode = true;
if (isMask) {
maskMouseUpEvent()
}
}
}, false);
document.addEventListener('mousemove', function (e) {
if (isSelecting) {
updateSelectionBox(selectionBox, startX, startY, e.pageX, e.pageY);
const selectionRect = selectionBox.getBoundingClientRect();
highlightElementsInRect(selectionRect, splitMode);
}
}, false);
if (!isMask) {
document.addEventListener('mouseup', function (e) {
if (isSelecting) {
isSelecting = false;
const selectionRect = selectionBox.getBoundingClientRect();
highlightElementsInRect(selectionRect, splitMode);
copyTextOfHighlightedElements(splitMode);
clearHighlightedElements();
document.body.removeChild(selectionBox);
selectionBox = null;
}
}, false);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
clearHighlightedElements();
if (selectionBox) {
document.body.removeChild(selectionBox);
selectionBox = null;
}
}
}, false);
})();
安装:在脚本管理器(篡改猴 Tampermonkey、暴力猴 Violentmonkey 等)面板中,添加新脚本
用法:默认 Alt + 左键
拖拽框选松开后自动复制框选文本(非拆分模式),如果松开左键后遇到选择框没有自动隐藏按 ESC
有两种选择模式(默认只启用非拆分模式,都启用需要自定义修饰键)
- 拆分模式:对嵌套换行标签的元素节点进行拆分,效果是换行标签前后文本,可单独复制
- 非拆分模式:不拆分嵌套换行标签的元素节点,直接复制整段文本
如果先对含换行标签的元素节点使用拆分模式,会破坏 html 结构(样式基本不变,页面刷新后会复原),再对其使用非拆分模式就无法直接复制整段文本
具体效果试下就懂了
脚本默认的修饰键会破坏原有 Alt + 左键
时在链接上选择文本的功能
可自定义修饰键,需要按住触发的键改为 true
,反之 false
// [modKey]:Mac 为 metaKey ,Windows 为 ctrlKey
// 非拆分模式修饰键
const nonSplitModeShortcut = { alt: true, [modKey]: false, shift: false };
// 拆分模式修饰键
const splitModeShortcut = { alt: false, [modKey]: false, shift: false };
// 是否屏蔽修饰键 + 左键的默认行为,true 为开启,false 为关闭
let isMask = false;
有在链接上选择部分文本需求的,可以试下这个扩展使用的, js 源码也能直接转成油猴脚本
GitHub - lcandy2/Select-like-a-Boss: Select link’s text just like a regular text (like in Opera’12 browser) - Select like a Boss
修饰键 + 左键单击也能快速复制
偶然发现用来摘录部分文本还挺方便的,还有在 ai 对话中没完全回答完成时快速复制(代码段也可以,在代码段的空白处点击或拖拽)
部分限制了复制文本的网站也能直接框选复制
可能有 bug,但不一定能修看情况,HTML 结构比较复杂的页面还是建议直接 OCR
如果有感兴趣的大佬能够完善或者优化这个脚本就更好了
待优化:
- 原生表格和 div 表格按照原格式复制,考虑到前端框架或组件的表格基本上都能导出数据,感觉意义不大,再者直接复制就能保留原格式
更新
- v0.2:改为双模式
- v0.3:双模式选择框样式区分
- v0.4:添加新的复制API,兼容更多页面
- v0.5:可在同一链接上点击或拖拽直接复制链接标题,由于此功能会破坏浏览器默认行为,默认是关闭的,需要手动开启
效果图
-
列复制
-
复制 AI 对话
相关讨论: