Greasy Fork

Greasy Fork is available in English.

网页元素屏蔽器

屏蔽任意网站上的元素,以原始比例缩略图显示屏蔽记录,修复确认屏蔽需点击两次问题

当前为 2025-03-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
// ==/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 = `
            <p>是否屏蔽此元素?</p>
            <button id="confirm">确认屏蔽</button>
            <button id="preview">预览</button>
            <button id="cancel">取消</button>
        `;
        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 = `
            <h3>当前域名屏蔽记录 (${domain})</h3>
            <ul id="block-items"></ul>
            <button id="close-list">关闭</button>
        `;
        document.body.appendChild(listWindow);

        const ul = document.getElementById('block-items');
        if (blockList.length === 0) {
            ul.innerHTML = '<li>暂无屏蔽记录</li>';
        } 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 });
})();