// ==UserScript== // @name 网页元素屏蔽器 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 屏蔽任意网站上的元素,以原始比例缩略图显示屏蔽记录,修复确认屏蔽需点击两次问题 // @author JerryChiang // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 添加样式 const style = document.createElement('style'); style.textContent = ` .highlight { outline: 2px solid red !important; background-color: rgba(255, 0, 0, 0.1) !important; } .blocker-popup { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.3); } .blocker-popup button { margin: 0 5px; } .blocker-list { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; z-index: 9999; max-height: 80vh; overflow-y: auto; width: 500px; } .blocker-list ul { list-style: none; padding: 0; } .blocker-list li { margin: 10px 0; display: flex; align-items: center; width: 100%; } .blocker-list img { max-width: 400px; max-height: 100px; object-fit: contain; border: 1px solid #ddd; flex-shrink: 0; } .blocker-list button { margin-left: auto; flex-shrink: 0; } `; document.head.appendChild(style); // 注册菜单 GM_registerMenuCommand('屏蔽设置', startBlockingMode); GM_registerMenuCommand('查看屏蔽记录', showBlockList); // 进入元素选择模式 function startBlockingMode() { alert('请将鼠标悬停在要屏蔽的元素上,高亮后点击选择。'); document.body.addEventListener('mouseover', highlightElement); document.body.addEventListener('click', selectElement, true); } // 高亮悬停元素 function highlightElement(event) { if (window.lastHighlighted) { window.lastHighlighted.classList.remove('highlight'); } event.target.classList.add('highlight'); window.lastHighlighted = event.target; } // 选择元素并弹出确认窗口 function selectElement(event) { event.preventDefault(); event.stopPropagation(); document.body.removeEventListener('mouseover', highlightElement); document.body.removeEventListener('click', selectElement, true); const selectedElement = event.target; window.lastHighlighted.classList.remove('highlight'); showConfirmation(selectedElement); } // 显示确认弹窗 function showConfirmation(element) { const popup = document.createElement('div'); popup.className = 'blocker-popup'; popup.innerHTML = `

是否屏蔽此元素?

`; document.body.appendChild(popup); let isPreviewHidden = false; const confirmBtn = document.getElementById('confirm'); confirmBtn.addEventListener('click', async () => { confirmBtn.disabled = true; // 禁用按钮,避免重复点击 try { await saveBlockWithThumbnail(element); // 等待异步操作完成 element.style.display = 'none'; // 隐藏元素 document.body.removeChild(popup); // 移除弹窗 } catch (e) { console.error('屏蔽失败:', e); confirmBtn.disabled = false; // 如果失败,恢复按钮 } }, { once: true }); // 确保事件只绑定一次 document.getElementById('preview').addEventListener('click', () => { if (!isPreviewHidden) { element.style.display = 'none'; isPreviewHidden = true; } else { element.style.display = ''; isPreviewHidden = false; } }); document.getElementById('cancel').addEventListener('click', () => { document.body.removeChild(popup); }); } // 保存屏蔽信息并生成缩略图(保持原始比例) async function saveBlockWithThumbnail(element) { const domain = window.location.hostname; const selector = getSelector(element); // 使用 html2canvas 生成截图 const canvas = await html2canvas(element, { scale: 1 }); const originalWidth = canvas.width; const originalHeight = canvas.height; // 计算缩放比例,限制 width ≤ 400,height ≤ 100 let scale = Math.min(400 / originalWidth, 100 / originalHeight, 1); const thumbnailCanvas = document.createElement('canvas'); thumbnailCanvas.width = originalWidth * scale; thumbnailCanvas.height = originalHeight * scale; const ctx = thumbnailCanvas.getContext('2d'); ctx.drawImage(canvas, 0, 0, thumbnailCanvas.width, thumbnailCanvas.height); const thumbnail = thumbnailCanvas.toDataURL('image/png'); let blocks = GM_getValue('blocks', {}); if (!blocks[domain]) { blocks[domain] = []; } if (!blocks[domain].some(item => item.selector === selector)) { blocks[domain].push({ selector, thumbnail }); GM_setValue('blocks', blocks); } } // 生成简单 CSS 选择器 function getSelector(element) { if (element.id) return `#${element.id}`; let path = []; while (element && element.nodeType === Node.ELEMENT_NODE) { let selector = element.tagName.toLowerCase(); if (element.className && typeof element.className === 'string') { selector += '.' + element.className.trim().replace(/\s+/g, '.'); } path.unshift(selector); element = element.parentElement; } return path.join(' > '); } // 应用屏蔽规则 function applyBlocks() { const domain = window.location.hostname; const blocks = GM_getValue('blocks', {}); if (blocks[domain]) { blocks[domain].forEach(item => { try { document.querySelectorAll(item.selector).forEach(el => { el.style.display = 'none'; }); } catch (e) { console.error(`无法应用选择器: ${item.selector}`, e); } }); } } // 显示屏蔽记录窗口(仅缩略图) function showBlockList() { const domain = window.location.hostname; const blocks = GM_getValue('blocks', {}); const blockList = blocks[domain] || []; const listWindow = document.createElement('div'); listWindow.className = 'blocker-list'; listWindow.innerHTML = `

当前域名屏蔽记录 (${domain})

`; document.body.appendChild(listWindow); const ul = document.getElementById('block-items'); if (blockList.length === 0) { ul.innerHTML = '
  • 暂无屏蔽记录
  • '; } else { blockList.forEach((item, index) => { const li = document.createElement('li'); const img = document.createElement('img'); img.src = item.thumbnail; const unblockBtn = document.createElement('button'); unblockBtn.textContent = '取消屏蔽'; unblockBtn.addEventListener('click', () => { removeBlock(domain, index); listWindow.remove(); applyBlocks(); showBlockList(); }); li.appendChild(img); li.appendChild(unblockBtn); ul.appendChild(li); }); } document.getElementById('close-list').addEventListener('click', () => { document.body.removeChild(listWindow); }); } // 删除屏蔽记录 function removeBlock(domain, index) { let blocks = GM_getValue('blocks', {}); if (blocks[domain] && blocks[domain][index]) { blocks[domain].splice(index, 1); if (blocks[domain].length === 0) { delete blocks[domain]; } GM_setValue('blocks', blocks); } } // 页面加载时立即应用屏蔽规则 applyBlocks(); const observer = new MutationObserver(() => applyBlocks()); observer.observe(document.body, { childList: true, subtree: true }); })();