Greasy Fork is available in English.
通过原生 UI 控制当前页面图片的显示和隐藏,支持持久化设置
// ==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
// ==/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 viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>`;
// 隐藏眼睛图标 SVG(隐藏状态)
const eyeOffIconSVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>`;
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();
})();