// ==UserScript== // @name Native Image Toggler // @namespace http://tampermonkey.net/ // @version 0.7 // @description 通过原生 UI 控制当前页面图片的显示和隐藏,支持持久化设置 // @author You // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @run-at document-start // @downloadURL none // ==/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, DEFAULT_POSITION); 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 控件的样式 const uiCSS = ` #image-toggler-ui { position: fixed; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 6px 14px; border-radius: 20px; cursor: grab; user-select: none; box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4), 0 1px 3px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.15); transition: box-shadow 0.25s ease, transform 0.15s ease, opacity 0.25s ease; font-size: 13px; font-weight: 600; letter-spacing: 0.02em; display: flex; align-items: center; gap: 6px; border: none; opacity: 0.88; white-space: nowrap; } #image-toggler-ui:hover { opacity: 1; transform: translateY(-1px); box-shadow: 0 6px 18px rgba(102, 126, 234, 0.5), 0 3px 8px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.2); } #image-toggler-ui.dragging { cursor: grabbing; transform: scale(1.06) !important; box-shadow: 0 12px 28px rgba(102, 126, 234, 0.55), 0 6px 12px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.2) !important; } #image-toggler-ui.visible-mode { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); box-shadow: 0 2px 10px rgba(17, 153, 142, 0.4), 0 1px 3px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.15); } #image-toggler-ui.visible-mode:hover { box-shadow: 0 6px 18px rgba(17, 153, 142, 0.5), 0 3px 8px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.2); } #image-toggler-ui.hidden-mode { background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); box-shadow: 0 2px 10px rgba(235, 51, 73, 0.4), 0 1px 3px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.15); } #image-toggler-ui.hidden-mode:hover { box-shadow: 0 6px 18px rgba(235, 51, 73, 0.5), 0 3px 8px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.2); } #image-toggler-icon svg { width: 16px; height: 16px; flex-shrink: 0; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.15)); } #image-toggler-text { line-height: 1; text-shadow: 0 1px 2px rgba(0,0,0,0.1); } /* 切换动画 */ @keyframes img-toggle-pop { 0% { transform: scale(0.95); } 50% { transform: scale(1.08); } 100% { transform: scale(1); } } #image-toggler-ui.toggle-animate { animation: img-toggle-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } `; // 初始化 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.right = uiPosition.right + 'px'; 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; // 重置拖动标记 e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; // 检测是否发生了实际移动 if (!hasDragged) { hasDragged = true; div.classList.add('dragging'); } const newLeft = e.clientX - dragOffsetX; const newTop = e.clientY - dragOffsetY; div.style.right = 'auto'; div.style.left = newLeft + 'px'; div.style.top = newTop + 'px'; }); document.addEventListener('mouseup', function() { if (!isDragging) return; isDragging = false; div.classList.remove('dragging'); // 只在真正发生过拖动时才保存新位置 if (hasDragged) { const rect = div.getBoundingClientRect(); uiPosition = { top: rect.top, right: window.innerWidth - rect.left - rect.width }; GM_setValue(positionKey, uiPosition); div.style.left = 'auto'; div.style.right = uiPosition.right + 'px'; } // 延迟重置 hasDragged setTimeout(() => { hasDragged = false; }, 10); }); div.addEventListener('click', function(e) { // 如果发生过拖动,不触发切换 if (hasDragged) { e.preventDefault(); e.stopPropagation(); return; } toggleImages(); }); 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 = ``; if (isImagesVisible) { el.innerHTML = `${eyeIconSVG}`; el.classList.add('visible-mode'); el.title = '当前:图片显示。点击隐藏图片'; } else { el.innerHTML = `${eyeOffIconSVG}`; el.classList.add('hidden-mode'); el.title = '当前:图片隐藏。点击显示图片'; } // 切换动画 el.classList.remove('toggle-animate'); void el.offsetWidth; // 触发 reflow el.classList.add('toggle-animate'); el.style.backgroundColor = ''; } // 切换状态 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(); })();