// ==UserScript== // @name Native Image Toggler // @namespace http://tampermonkey.net/ // @version 1.0 // @description 通过原生 UI 控制当前页面图片的显示和隐藏,支持持久化设置 // @author You // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/567837/Native%20Image%20Toggler.user.js // @updateURL https://update.greasyfork.icu/scripts/567837/Native%20Image%20Toggler.meta.js // ==/UserScript== (function() { 'use strict'; // 配置常量 const STORAGE_KEY_PREFIX = 'img_toggle_'; const STYLE_ID = 'native-image-toggler-style'; const POSITION_KEY = 'img_toggle_position_'; const UI_VISIBLE_KEY = 'img_toggle_ui_visible_'; const DEFAULT_POSITION = { top: 10, right: 20 }; // 获取当前域名的设置键 const hostname = window.location.hostname; const storageKey = STORAGE_KEY_PREFIX + hostname; const positionKey = POSITION_KEY + hostname; // 状态:true 表示图片显示(默认),false 表示图片隐藏 // 默认所有网站都是显示的,除非用户手动关闭 let isImagesVisible = GM_getValue(storageKey, true); let uiPosition = GM_getValue(positionKey, { top: 10, left: window.innerWidth - 20 - 100 }); // 默认右上角,假设控件宽度约100px let isUIVisible = GM_getValue(UI_VISIBLE_KEY + hostname, true); let isUIInitialized = false; // 防止重复初始化 UI // CSS 样式:隐藏图片的样式 const hideImageCSS = ` img, image, picture, svg, canvas, video, iframe { opacity: 0 !important; visibility: hidden !important; pointer-events: none !important; } * { background-image: none !important; } /* 排除我们自己的 UI */ #image-toggler-ui, #image-toggler-ui * { opacity: 1 !important; visibility: visible !important; pointer-events: auto !important; background-image: initial !important; } `; // CSS 样式:UI 控件的样式 - Windows 11 Fluent Design 风格 const uiCSS = ` #image-toggler-ui { position: fixed; z-index: 2147483647; font-family: "Segoe UI Variable", "Segoe UI", system-ui, sans-serif; background-color: #f3f3f3; color: #1a1a1a; padding: 5px 12px; border-radius: 4px; cursor: grab; user-select: none; border: 1px solid rgba(0,0,0,0.08); transition: background-color 0.1s ease, box-shadow 0.15s ease, opacity 0.2s ease; font-size: 12px; font-weight: 400; display: flex; align-items: center; gap: 5px; opacity: 0.95; white-space: nowrap; } #image-toggler-ui:hover { background-color: #e5e5e5; box-shadow: 0 2px 6px rgba(0,0,0,0.06); } #image-toggler-ui:active { background-color: #dcdcdc; } #image-toggler-ui.dragging { cursor: grabbing; transform: scale(1.02); box-shadow: 0 4px 12px rgba(0,0,0,0.12); } #image-toggler-icon svg { width: 14px; height: 14px; flex-shrink: 0; } #image-toggler-text { line-height: 1; } /* 切换动画 - 轻微闪烁 */ @keyframes img-toggle-flash { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } } #image-toggler-ui.toggle-animate { animation: img-toggle-flash 0.2s ease; } /* 暗色模式适配 */ @media (prefers-color-scheme: dark) { #image-toggler-ui { background-color: #323232; color: #fff; border-color: rgba(255,255,255,0.08); } #image-toggler-ui:hover { background-color: #454545; box-shadow: 0 2px 6px rgba(0,0,0,0.25); } #image-toggler-ui:active { background-color: #505050; } } `; // 初始化 function init() { // 尽早应用状态(如果需要隐藏图片) applyState(); // 等待 body 加载完成后创建 UI if (document.body) { setupUI(); applyUIState(); } else { document.addEventListener('DOMContentLoaded', setupUI); document.addEventListener('DOMContentLoaded', applyUIState); } // 注册菜单命令作为备用 GM_registerMenuCommand("切换图片显示/隐藏", toggleImages); GM_registerMenuCommand("切换控件显示/隐藏", toggleUIVisibility); // 添加键盘快捷键 Ctrl+Shift+I document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey && e.key === 'I') { e.preventDefault(); toggleImages(); } }); } function setupUI() { // 只在顶层窗口创建 UI,避免在 iframe 中重复创建 if (window.self !== window.top) return; // 防止重复创建 if (isUIInitialized) return; if (document.getElementById('image-toggler-ui')) { isUIInitialized = true; return; } // 添加 UI 样式 GM_addStyle(uiCSS); // 创建 UI createUI(); isUIInitialized = true; } // 创建 UI 控件 function createUI() { const div = document.createElement('div'); div.id = 'image-toggler-ui'; div.style.top = uiPosition.top + 'px'; div.style.left = uiPosition.left + 'px'; div.style.right = 'auto'; let isDragging = false; let hasDragged = false; // 用于标记是否真正发生了拖动 let dragOffsetX = 0; let dragOffsetY = 0; div.addEventListener('mousedown', function(e) { if (e.button !== 0) return; // 阻止默认行为,但不要阻止事件传播,避免影响点击 const rect = div.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; isDragging = true; hasDragged = false; // 重置拖动标记 }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; // 检测是否发生了实际移动(超过 3 像素才算拖动) const currentX = e.clientX - dragOffsetX; const currentY = e.clientY - dragOffsetY; const rect = div.getBoundingClientRect(); const deltaX = Math.abs(currentX - rect.left); const deltaY = Math.abs(currentY - rect.top); if (!hasDragged && (deltaX > 3 || deltaY > 3)) { hasDragged = true; div.classList.add('dragging'); } if (hasDragged) { div.style.right = 'auto'; div.style.left = currentX + 'px'; div.style.top = currentY + 'px'; } }); document.addEventListener('mouseup', function() { if (!isDragging) return; isDragging = false; div.classList.remove('dragging'); // 只在真正发生过拖动时才保存新位置 if (hasDragged) { const rect = div.getBoundingClientRect(); // 保存当前位置(使用 left 而不是 right,避免定位切换导致的偏移) uiPosition = { top: rect.top, left: rect.left }; GM_setValue(positionKey, uiPosition); // 保持使用 left 定位,不切换回 right div.style.right = 'auto'; div.style.left = rect.left + 'px'; div.style.top = rect.top + 'px'; } // 不在这里重置 hasDragged,让 click 事件能正确判断 }); div.addEventListener('click', function(e) { // 如果发生过拖动,不触发切换 if (hasDragged) { e.preventDefault(); e.stopPropagation(); } // 点击后立即重置 hasDragged,为下次做准备 setTimeout(() => { hasDragged = false; }, 0); if (!hasDragged) { toggleImages(); } }); // 创建 UI 内容 const iconSpan = document.createElement('span'); iconSpan.id = 'image-toggler-icon'; const textSpan = document.createElement('span'); textSpan.id = 'image-toggler-text'; div.appendChild(iconSpan); div.appendChild(textSpan); div.title = '拖动调整位置 | 点击切换图片显隐 | Ctrl+Shift+I 快捷键切换'; document.body.appendChild(div); updateUI(div); // 创建右上角 UI } // 更新 UI 显示 function updateUI(element) { const el = element || document.getElementById('image-toggler-ui'); if (!el) return; el.classList.remove('visible-mode', 'hidden-mode'); // 眼睛图标 SVG(显示状态) const eyeIconSVG = ``; // 隐藏眼睛图标 SVG(隐藏状态) const eyeOffIconSVG = ``; const iconSpan = el.querySelector('#image-toggler-icon'); const textSpan = el.querySelector('#image-toggler-text'); if (isImagesVisible) { if (iconSpan) iconSpan.innerHTML = eyeIconSVG; if (textSpan) textSpan.textContent = '显'; el.title = '当前:图片显示。点击隐藏图片'; } else { if (iconSpan) iconSpan.innerHTML = eyeOffIconSVG; if (textSpan) textSpan.textContent = '隐'; el.title = '当前:图片隐藏。点击显示图片'; } // 切换动画 el.classList.remove('toggle-animate'); void el.offsetWidth; el.classList.add('toggle-animate'); } // 切换状态 function toggleImages() { isImagesVisible = !isImagesVisible; GM_setValue(storageKey, isImagesVisible); applyState(); updateUI(); } // 切换 UI 可见性 function toggleUIVisibility() { isUIVisible = !isUIVisible; GM_setValue(UI_VISIBLE_KEY, isUIVisible); applyUIState(); } // 应用 UI 可见性状态 function applyUIState() { const ui = document.getElementById('image-toggler-ui'); if (ui) { ui.style.display = isUIVisible ? 'flex' : 'none'; } } // 应用状态(添加或移除 CSS) function applyState() { let styleEl = document.getElementById(STYLE_ID); // 确保 head 存在,通常在 document-start 时 head 也是存在的(除了极早的情况) // 如果 head 不存在,稍微延时重试 if (!document.head) { setTimeout(applyState, 10); return; } if (!isImagesVisible) { // 如果需要隐藏图片,且样式元素不存在,则添加 if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = STYLE_ID; styleEl.textContent = hideImageCSS; document.head.appendChild(styleEl); } } else { // 如果需要显示图片,且样式元素存在,则移除 if (styleEl) { styleEl.remove(); } } } // 启动 init(); })();