// ==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();
})();