添加区域模式,优化提示

This commit is contained in:
ytc1012
2025-12-11 11:44:54 +08:00
parent 86c47ba772
commit ea0976b75b
6 changed files with 433 additions and 64 deletions

View File

@@ -1,7 +1,8 @@
{
"permissions": {
"allow": [
"Bash(wc:*)"
"Bash(wc:*)",
"Bash(cat:*)"
]
}
}

View File

@@ -10,59 +10,30 @@
position: relative !important;
}
/* 元素模式下的悬停效果 */
body.blurtext-mode .blurtext-blurred:hover::after {
content: '点击取消模糊' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
/* 元素模式下的悬停效果 - 移除伪元素方案,改用 JS 动态创建 */
/* body.blurtext-mode .blurtext-blurred:hover::after 已移除 */
/* 元素模式悬停提示工具栏JS 动态创建) */
.blurtext-element-tooltip {
position: fixed !important;
background: rgba(102, 126, 234, 0.95) !important;
color: white !important;
padding: 4px 8px !important;
border-radius: 4px !important;
font-size: 12px !important;
white-space: nowrap !important;
z-index: 999999 !important;
pointer-events: none !important;
filter: none !important;
}
/* 文本选择模式包裹的元素悬停效果 */
.blurtext-selection-wrapped {
transition: all 0.1s ease !important;
}
.blurtext-selection-wrapped:hover {
background-color: rgba(102, 126, 234, 0.2) !important;
outline: 2px solid rgba(102, 126, 234, 0.5) !important;
outline-offset: 2px !important;
}
.blurtext-selection-wrapped:hover::after {
content: '🔓 点击恢复' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
background: rgba(102, 126, 234, 0.98) !important;
color: white !important;
padding: 6px 12px !important;
border-radius: 6px !important;
font-size: 13px !important;
font-weight: 600 !important;
white-space: nowrap !important;
z-index: 999999 !important;
z-index: 2147483647 !important;
pointer-events: none !important;
filter: none !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
animation: blurtext-tooltipPop 0.15s ease !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
animation: blurtext-tooltipFadeIn 0.15s ease !important;
}
@keyframes blurtext-tooltipPop {
@keyframes blurtext-tooltipFadeIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
transform: translate(-50%, -50%) scale(0.9);
}
to {
opacity: 1;
@@ -70,6 +41,42 @@ body.blurtext-mode .blurtext-blurred:hover::after {
}
}
/* 文本选择模式包裹的元素悬停效果 */
.blurtext-selection-wrapped {
display: inline !important;
position: relative !important;
cursor: pointer !important;
}
/* 移除伪元素提示,改用 JS 动态创建(避免被模糊效果影响) */
/* 文本选择错误提示(显示在选区下方) */
.blurtext-selection-error {
background: #f44336 !important;
color: white !important;
padding: 8px 16px !important;
border-radius: 6px !important;
font-size: 13px !important;
font-weight: 500 !important;
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4) !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
animation: blurtext-errorPop 0.2s ease !important;
pointer-events: none !important;
white-space: nowrap !important;
}
@keyframes blurtext-errorPop {
0% {
opacity: 0;
transform: translateX(-50%) translateY(-5px) scale(0.9);
}
100% {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
}
}
/* 模糊模式下的鼠标样式 */
body.blurtext-mode * {
cursor: crosshair !important;
@@ -149,7 +156,52 @@ body.blurtext-mode * {
}
}
/* 文本选择模式包裹的元素 */
.blurtext-selection-wrapped {
display: inline !important;
/* ========== 区域模式样式 ========== */
/* 绘制框(拖动时显示) */
.blurtext-drawing-box {
border: 2px dashed #667eea !important;
background: rgba(102, 126, 234, 0.1) !important;
pointer-events: none !important;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5) !important;
}
/* 区域覆盖层 */
.blurtext-area-overlay {
background: rgba(255, 255, 255, 0.1) !important;
backdrop-filter: blur(var(--blur-intensity, 10px)) !important;
-webkit-backdrop-filter: blur(var(--blur-intensity, 10px)) !important;
border: none !important;
box-sizing: border-box !important;
transition: all 0.2s ease !important;
user-select: none !important;
cursor: pointer !important;
}
.blurtext-area-overlay:hover {
outline: 2px solid rgba(102, 126, 234, 0.5) !important;
outline-offset: -2px !important;
background: rgba(255, 255, 255, 0.15) !important;
}
/* 区域覆盖层悬停提示 */
.blurtext-area-overlay:hover::after {
content: '点击恢复此区域' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
background: rgba(102, 126, 234, 0.95) !important;
color: white !important;
padding: 6px 12px !important;
border-radius: 6px !important;
font-size: 13px !important;
font-weight: 600 !important;
white-space: nowrap !important;
z-index: 999999 !important;
pointer-events: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
filter: none !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
}

View File

@@ -5,11 +5,19 @@
console.log('[BlurText] Content script loaded');
let isBlurMode = false;
let blurMode = 'element'; // 'element' or 'selection'
let blurMode = 'element'; // 'element', 'selection', or 'area'
let blurIntensity = 10;
let blurredElements = new Set();
let hintElement = null;
let blurButton = null; // 浮动模糊按钮
let elementTooltip = null; // 元素模式悬停提示
// 区域模式相关变量
let isDrawing = false;
let startX = 0;
let startY = 0;
let drawingBox = null;
let areaOverlays = []; // 存储所有区域覆盖层
// 初始化:从存储中加载配置
chrome.storage.local.get(['blurIntensity'], (result) => {
@@ -53,7 +61,12 @@
function enableBlurMode() {
console.log('[BlurText] Enabling blur mode, mode:', blurMode);
const modeText = blurMode === 'selection' ? '文本选择模式 - 拖动选择文字后点击模糊按钮' : '元素模式 - 点击元素进行模糊';
let modeText = '元素模式 - 点击元素进行模糊';
if (blurMode === 'selection') {
modeText = '文本选择模式 - 拖动选择文字后点击模糊按钮';
} else if (blurMode === 'area') {
modeText = '区域模式 - 拖动鼠标绘制模糊区域';
}
showHint(`模糊模式已开启 - ${modeText},按 ESC 退出`);
// 根据模式添加对应的样式和事件
@@ -65,9 +78,15 @@
} else if (blurMode === 'selection') {
// 文本选择模式不需要 crosshair 光标
document.addEventListener('mouseup', handleTextSelection, true);
} else if (blurMode === 'area') {
// 区域模式:使用 crosshair 光标并添加绘制事件
document.body.classList.add('blurtext-mode');
document.addEventListener('mousedown', handleAreaMouseDown, true);
document.addEventListener('mousemove', handleAreaMouseMove, true);
document.addEventListener('mouseup', handleAreaMouseUp, true);
}
// 键盘事件对两种模式都需要
// 键盘事件对所有模式都需要
document.addEventListener('keydown', handleKeydown, true);
console.log('[BlurText] Event listeners attached');
@@ -78,6 +97,8 @@
document.body.classList.remove('blurtext-mode');
hideHint();
hideBlurButton();
hideDrawingBox();
hideElementTooltip();
// 移除所有事件监听
document.removeEventListener('click', handleClick, true);
@@ -85,6 +106,9 @@
document.removeEventListener('mouseover', handleMouseOver, true);
document.removeEventListener('mouseout', handleMouseOut, true);
document.removeEventListener('mouseup', handleTextSelection, true);
document.removeEventListener('mousedown', handleAreaMouseDown, true);
document.removeEventListener('mousemove', handleAreaMouseMove, true);
document.removeEventListener('mouseup', handleAreaMouseUp, true);
// 清除所有高亮和预览
removeAllHighlights();
@@ -102,10 +126,15 @@
document.removeEventListener('mouseover', handleMouseOver, true);
document.removeEventListener('mouseout', handleMouseOut, true);
document.removeEventListener('mouseup', handleTextSelection, true);
document.removeEventListener('mousedown', handleAreaMouseDown, true);
document.removeEventListener('mousemove', handleAreaMouseMove, true);
document.removeEventListener('mouseup', handleAreaMouseUp, true);
// 清除旧模式的UI元素
hideBlurButton();
removeAllHighlights();
hideDrawingBox();
hideElementTooltip();
// 根据新模式添加事件监听和样式
if (blurMode === 'element') {
@@ -118,6 +147,13 @@
// 文本选择模式不需要 crosshair 光标
document.addEventListener('mouseup', handleTextSelection, true);
showHint('已切换到文本选择模式 - 拖动选择文字后点击模糊按钮', 3000);
} else if (blurMode === 'area') {
// 区域模式
document.body.classList.add('blurtext-mode');
document.addEventListener('mousedown', handleAreaMouseDown, true);
document.addEventListener('mousemove', handleAreaMouseMove, true);
document.addEventListener('mouseup', handleAreaMouseUp, true);
showHint('已切换到区域模式 - 拖动鼠标绘制模糊区域', 3000);
}
console.log('[BlurText] Mode switched successfully');
@@ -222,6 +258,33 @@
}
}
// 显示元素悬停提示
function showElementTooltip(element, text) {
// 移除旧提示
hideElementTooltip();
// 获取元素位置
const rect = element.getBoundingClientRect();
// 创建提示元素
elementTooltip = document.createElement('div');
elementTooltip.className = 'blurtext-element-tooltip';
elementTooltip.textContent = text;
elementTooltip.style.left = `${rect.left + rect.width / 2}px`;
elementTooltip.style.top = `${rect.top + rect.height / 2}px`;
elementTooltip.style.transform = 'translate(-50%, -50%)';
document.body.appendChild(elementTooltip);
}
// 隐藏元素悬停提示
function hideElementTooltip() {
if (elementTooltip && elementTooltip.parentNode) {
elementTooltip.parentNode.removeChild(elementTooltip);
elementTooltip = null;
}
}
// 模糊选中的文本
function blurSelectedText() {
const selection = window.getSelection();
@@ -230,21 +293,76 @@
try {
const range = selection.getRangeAt(0);
// 获取选中的文本内容
const selectedText = range.toString();
// 验证选区是否符合要求
if (!selectedText.trim()) {
console.log('[BlurText] No text selected');
hideBlurButton();
return;
}
// 限制:不允许包含换行
if (selectedText.includes('\n') || selectedText.includes('\r')) {
console.log('[BlurText] Selection contains line breaks');
showSelectionError(selection, '不支持跨行选择');
hideBlurButton();
return;
}
// 限制:不允许包含多个空格(允许单个空格)
if (/\s{2,}/.test(selectedText)) {
console.log('[BlurText] Selection contains multiple spaces');
showSelectionError(selection, '不支持多个连续空格');
hideBlurButton();
return;
}
// 限制:检查是否跨越多个元素
const startContainer = range.startContainer;
const endContainer = range.endContainer;
// 如果起始和结束容器不同,则跨越了多个节点
if (startContainer !== endContainer) {
console.log('[BlurText] Selection spans multiple elements');
showSelectionError(selection, '不支持跨元素选择');
hideBlurButton();
return;
}
// 限制:只能选择文本节点
if (startContainer.nodeType !== Node.TEXT_NODE) {
console.log('[BlurText] Selection is not in a text node');
showSelectionError(selection, '请选择纯文本内容');
hideBlurButton();
return;
}
// 创建 span 包裹选中的文本
const span = document.createElement('span');
span.className = 'blurtext-blurred blurtext-selection-wrapped';
span.style.setProperty('--blur-intensity', `${blurIntensity}px`);
span.style.cursor = 'pointer';
span.title = '点击恢复此段文本';
// 添加鼠标悬停事件,显示恢复提示
span.addEventListener('mouseenter', () => {
showElementTooltip(span, '点击恢复此文本');
});
span.addEventListener('mouseleave', () => {
hideElementTooltip();
});
// 添加点击事件,允许单独恢复
span.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
hideElementTooltip(); // 点击后立即隐藏提示
unblurSelectionSpan(span);
});
// 包裹选中的内容
// 使用 surroundContents在严格限制下应该不会失败
range.surroundContents(span);
// 添加到模糊元素集合
@@ -257,16 +375,43 @@
// 隐藏按钮
hideBlurButton();
// 显示提示
showHint('文本已模糊(点击可单独恢复)', 2000);
} catch (error) {
console.error('[BlurText] Error blurring selection:', error);
showHint('无法模糊该选区可能包含复杂的HTML结构', 3000);
showSelectionError(selection, '无法模糊该选区');
hideBlurButton();
}
}
// 在选区位置显示错误提示
function showSelectionError(selection, message) {
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
// 创建错误提示元素
const errorTooltip = document.createElement('div');
errorTooltip.className = 'blurtext-selection-error';
errorTooltip.textContent = message;
errorTooltip.style.position = 'fixed';
errorTooltip.style.left = `${rect.left + rect.width / 2}px`;
errorTooltip.style.top = `${rect.bottom + 10}px`;
errorTooltip.style.transform = 'translateX(-50%)';
errorTooltip.style.zIndex = '2147483647';
document.body.appendChild(errorTooltip);
// 清除选择
selection.removeAllRanges();
// 2秒后自动移除
setTimeout(() => {
if (errorTooltip.parentNode) {
errorTooltip.parentNode.removeChild(errorTooltip);
}
}, 2000);
}
// 恢复单个选择模式的模糊段落
function unblurSelectionSpan(span) {
if (!span || !span.parentNode) return;
@@ -276,7 +421,7 @@
// 从集合中移除
blurredElements.delete(span);
// 获取 span 的内容
// 获取 span 的内容(保持子节点结构)
const fragment = document.createDocumentFragment();
while (span.firstChild) {
fragment.appendChild(span.firstChild);
@@ -285,9 +430,6 @@
// 用原内容替换 span
span.parentNode.replaceChild(fragment, span);
// 显示提示
showHint('已恢复此段文本', 1500);
console.log('[BlurText] Selection span removed, remaining elements:', blurredElements.size);
}
@@ -296,13 +438,20 @@
if (!isBlurMode || blurMode !== 'element') return;
const target = e.target;
if (target.classList.contains('blurtext-hint') ||
target.classList.contains('blurtext-blurred')) {
if (target.classList.contains('blurtext-hint')) {
return;
}
// 移除之前的高亮
// 如果是已模糊的元素,显示恢复提示(不添加高亮
if (target.classList.contains('blurtext-blurred')) {
removeAllHighlights(); // 移除其他高亮
showElementTooltip(target, '点击取消模糊');
return;
}
// 移除之前的高亮和提示
removeAllHighlights();
hideElementTooltip();
// 直接高亮元素
target.classList.add('blurtext-highlight');
@@ -320,6 +469,9 @@
function handleMouseOut(e) {
if (!isBlurMode || blurMode !== 'element') return;
// 隐藏提示
hideElementTooltip();
// 延迟移除,避免在元素间移动时闪烁
setTimeout(() => {
// 检查鼠标是否还在模糊模式下的元素上
@@ -346,6 +498,9 @@
element.classList.remove('blurtext-blurred');
element.style.removeProperty('--blur-intensity');
blurredElements.delete(element);
// 隐藏提示气泡(因为元素已不再模糊)
hideElementTooltip();
} else {
// 添加模糊
console.log('[BlurText] Adding blur to element, intensity:', blurIntensity);
@@ -364,12 +519,160 @@
});
blurredElements.clear();
// 清除所有区域覆盖层
areaOverlays.forEach(overlay => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
});
areaOverlays = [];
// 如果在模糊模式下,显示提示
if (isBlurMode) {
showHint('已清除所有模糊效果', 2000);
}
}
// ========== 区域模式相关函数 ==========
// 处理区域模式鼠标按下
function handleAreaMouseDown(e) {
if (!isBlurMode || blurMode !== 'area') return;
// 忽略提示元素和已有的区域覆盖层
if (e.target.classList.contains('blurtext-hint') ||
e.target.classList.contains('blurtext-area-overlay') ||
e.target.classList.contains('blurtext-area-close')) {
return;
}
// 检查是否点击了关闭按钮
if (e.target.classList.contains('blurtext-area-close')) {
const overlay = e.target.parentElement;
removeAreaOverlay(overlay);
e.preventDefault();
e.stopPropagation();
return;
}
isDrawing = true;
startX = e.pageX;
startY = e.pageY;
// 创建绘制框
drawingBox = document.createElement('div');
drawingBox.className = 'blurtext-drawing-box';
drawingBox.style.position = 'absolute';
drawingBox.style.left = startX + 'px';
drawingBox.style.top = startY + 'px';
drawingBox.style.width = '0px';
drawingBox.style.height = '0px';
drawingBox.style.zIndex = '2147483646';
document.body.appendChild(drawingBox);
e.preventDefault();
e.stopPropagation();
}
// 处理区域模式鼠标移动
function handleAreaMouseMove(e) {
if (!isBlurMode || blurMode !== 'area' || !isDrawing || !drawingBox) return;
const currentX = e.pageX;
const currentY = e.pageY;
const width = Math.abs(currentX - startX);
const height = Math.abs(currentY - startY);
const left = Math.min(currentX, startX);
const top = Math.min(currentY, startY);
drawingBox.style.left = left + 'px';
drawingBox.style.top = top + 'px';
drawingBox.style.width = width + 'px';
drawingBox.style.height = height + 'px';
}
// 处理区域模式鼠标释放
function handleAreaMouseUp(e) {
if (!isBlurMode || blurMode !== 'area' || !isDrawing || !drawingBox) return;
isDrawing = false;
const width = parseInt(drawingBox.style.width);
const height = parseInt(drawingBox.style.height);
// 只有当区域足够大时才创建模糊覆盖层(至少 20x20 像素)
if (width > 20 && height > 20) {
createAreaOverlay(
parseInt(drawingBox.style.left),
parseInt(drawingBox.style.top),
width,
height
);
}
// 移除绘制框
if (drawingBox.parentNode) {
drawingBox.parentNode.removeChild(drawingBox);
}
drawingBox = null;
e.preventDefault();
e.stopPropagation();
}
// 创建区域覆盖层
function createAreaOverlay(left, top, width, height) {
const overlay = document.createElement('div');
overlay.className = 'blurtext-area-overlay';
overlay.style.position = 'absolute';
overlay.style.left = left + 'px';
overlay.style.top = top + 'px';
overlay.style.width = width + 'px';
overlay.style.height = height + 'px';
overlay.style.setProperty('--blur-intensity', `${blurIntensity}px`);
overlay.style.zIndex = '2147483645';
// 点击覆盖层恢复区域
overlay.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
removeAreaOverlay(overlay);
});
document.body.appendChild(overlay);
areaOverlays.push(overlay);
// 添加到模糊元素集合(用于统一管理强度)
blurredElements.add(overlay);
console.log('[BlurText] Area overlay created, total overlays:', areaOverlays.length);
}
// 移除区域覆盖层
function removeAreaOverlay(overlay) {
const index = areaOverlays.indexOf(overlay);
if (index > -1) {
areaOverlays.splice(index, 1);
}
blurredElements.delete(overlay);
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
console.log('[BlurText] Area overlay removed, remaining:', areaOverlays.length);
}
// 隐藏绘制框
function hideDrawingBox() {
if (drawingBox && drawingBox.parentNode) {
drawingBox.parentNode.removeChild(drawingBox);
drawingBox = null;
}
isDrawing = false;
}
// ========== 区域模式函数结束 ==========
// 更新所有已模糊元素的强度
function updateAllBlurIntensity() {
blurredElements.forEach(element => {

View File

@@ -168,7 +168,7 @@
.mode-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
}
@@ -243,6 +243,10 @@
<span class="mode-icon">📝</span>
<span class="mode-label">文本选择</span>
</button>
<button class="mode-btn" id="modeArea" data-mode="area">
<span class="mode-icon">🔲</span>
<span class="mode-label">区域模式</span>
</button>
</div>
</div>

View File

@@ -13,6 +13,7 @@ const intensityValue = document.getElementById('intensityValue');
const statusDiv = document.getElementById('status');
const modeElementBtn = document.getElementById('modeElement');
const modeSelectionBtn = document.getElementById('modeSelection');
const modeAreaBtn = document.getElementById('modeArea');
const usageTip = document.getElementById('usageTip');
// 从存储中加载模糊强度
@@ -41,7 +42,7 @@ blurIntensitySlider.addEventListener('input', (e) => {
});
// 模式切换
[modeElementBtn, modeSelectionBtn].forEach(btn => {
[modeElementBtn, modeSelectionBtn, modeAreaBtn].forEach(btn => {
btn.addEventListener('click', () => {
const mode = btn.dataset.mode;
currentMode = mode;
@@ -75,7 +76,7 @@ function updateUsageTip(mode) {
3. 点击元素进行模糊<br>
4. 再次点击取消模糊
`;
} else {
} else if (mode === 'selection') {
usageTip.innerHTML = `
<strong>文本选择模式:</strong><br>
1. 点击"开启模糊模式"<br>
@@ -83,6 +84,14 @@ function updateUsageTip(mode) {
3. 点击浮动按钮模糊文本<br>
4. 可多次选择和模糊
`;
} else if (mode === 'area') {
usageTip.innerHTML = `
<strong>区域模式:</strong><br>
1. 点击"开启模糊模式"<br>
2. 拖动鼠标绘制矩形区域<br>
3. 释放鼠标创建模糊区域<br>
4. 点击区域恢复
`;
}
}

View File

@@ -136,7 +136,7 @@
<div class="test-section">
<h2>📝 测试区域 1普通文本</h2>
<div class="test-content">
<p>这是一段普通的文本内容,你可以点击这段文字来测试模糊功能。</p>
<p>这是一段普通的文本内容,你可以点击这段文字来测试模糊功能。这是一段普通的文本内容,你可以点击这段文字来测试模糊功能。这是一段普通的文本内容,你可以点击这段文字来测试模糊功能。</p>
<p>模糊功能应该可以立即生效,不需要屏幕录制或分享。</p>
</div>
</div>