+
// ==UserScript==
// @name 图片悬停放大工具
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 鼠标悬停显示放大按钮,点击放大并支持滚轮缩放
// @author clownvary
// @match *://*/*
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
// 添加必要的样式
GM_addStyle(`
.zoom-btn {
position: absolute;
top: 5px;
right: 5px;
background: rgba(0,0,0,0.5);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 9999;
}
.zoom-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 10000;
}
.zoomed-img {
max-width: 90%;
max-height: 90%;
object-fit: contain;
transform-origin: center center;
user-select: none;
-webkit-user-drag: none;
}
`);
(function() {
'use strict';
// 网站特定规则配置
const siteRules = {
'example.com': {
selector: '.original-image', // 原图元素选择器
attribute: 'src', // 原图URL属性
parentSelector: '.image-wrapper', // 父元素选择器
siblingSelector: '.hd-image', // 兄弟元素选择器
}
};
// 获取当前网站的规则
function getSiteRule() {
const hostname = window.location.hostname;
return siteRules[hostname];
}
// 智能查找原图URL
function getOriginalImageUrl(img) {
// 处理Twitter图片
if (img.src && img.src.includes('twimg.com')) {
return img.src.replace(/\?format=.+$/, '?format=jpg&name=orig');
}
// 1. 检查用户是否手动设置了该图片的原图URL
const manualUrls = GM_getValue('manualImageUrls', {});
if (manualUrls[img.src]) {
return manualUrls[img.src];
}
// 2. 检查网站特定规则
const siteRule = getSiteRule();
if (siteRule) {
// 根据规则查找原图
if (siteRule.parentSelector) {
const parent = img.closest(siteRule.parentSelector);
if (parent) {
const originalImg = parent.querySelector(siteRule.selector);
if (originalImg && originalImg[siteRule.attribute]) {
return originalImg[siteRule.attribute];
}
}
}
}
// 3. 常见属性检查
const commonAttributes = [
'data-original',
'data-src',
'data-full',
'data-zoom-src',
'data-big',
'data-actualsrc',
'data-original-src'
];
for (const attr of commonAttributes) {
const value = img.getAttribute(attr);
if (value) return value;
}
// 4. 检查父级a标签
const parentLink = img.closest('a');
if (parentLink && /\.(jpe?g|png|gif|webp)($|\?)/i.test(parentLink.href)) {
return parentLink.href;
}
// 5. 检查兄弟节点
const siblings = img.parentElement.children;
for (const sibling of siblings) {
if (sibling !== img && sibling.tagName === 'IMG') {
const siblingUrl = sibling.src || sibling.getAttribute('data-src');
if (siblingUrl && siblingUrl.includes('original')) {
return siblingUrl;
}
}
}
// 6. 尝试修改URL参数来获取大图
const urlPatterns = [
{ pattern: /(_thumb|_small|_mini)/i, replacement: '' },
{ pattern: /\b(width|height|size)=\d+/i, replacement: '' },
{ pattern: /\bw=\d+/i, replacement: '' },
{ pattern: /\bq=\d+/i, replacement: 'q=100' }
];
let originalUrl = img.src;
for (const {pattern, replacement} of urlPatterns) {
if (pattern.test(originalUrl)) {
return originalUrl.replace(pattern, replacement);
}
}
// 最后返回原始src
return img.src;
}
// 添加右键菜单处理
function addContextMenu(img, zoomBtn) {
zoomBtn.addEventListener('contextmenu', (e) => {
e.preventDefault();
const manualUrl = prompt('请输入此图片的原图URL:');
if (manualUrl) {
const manualUrls = GM_getValue('manualImageUrls', {});
manualUrls[img.src] = manualUrl;
GM_setValue('manualImageUrls', manualUrls);
alert('设置成功!');
}
});
}
// 为所有图片添加放大功能
function initializeImages() {
// 处理普通的img标签
document.querySelectorAll('img').forEach(handleImage);
// 针对Twitter的特定选择器
if (window.location.hostname.includes('twitter.com')) {
document.querySelectorAll('div[style*="background-image"]').forEach(handleBackgroundImage);
}
// 其他网站的背景图片处理(可以根据需要添加特定选择器)
const bgImageSelectors = [
'div[style*="background-image"]',
'.image-bg', // 示例:特定类名
'[data-bg-image]' // 示例:特定属性
];
if (!window.location.hostname.includes('twitter.com')) {
document.querySelectorAll(bgImageSelectors.join(',')).forEach(handleBackgroundImage);
}
}
// 处理背景图片元素
function handleBackgroundImage(element) {
if (element.dataset.zoomInitialized) return;
element.dataset.zoomInitialized = 'true';
// 获取背景图片URL
const bgImage = window.getComputedStyle(element).backgroundImage;
const url = bgImage.replace(/^url\(['"]?(.+?)['"]?\)$/, '$1');
if (!url || url === 'none') return;
// 创建放大按钮
const zoomBtn = document.createElement('div');
zoomBtn.className = 'zoom-btn';
zoomBtn.innerHTML = '+';
// 确保父元素是relative定位
const position = window.getComputedStyle(element).position;
if (position === 'static') {
element.style.position = 'relative';
}
element.appendChild(zoomBtn);
// 鼠标悬停显示放大按钮
element.addEventListener('mouseenter', () => {
zoomBtn.style.display = 'flex';
});
element.addEventListener('mouseleave', () => {
zoomBtn.style.display = 'none';
});
// 点击放大按钮
zoomBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const originalUrl = url.includes('twimg.com') ?
url.replace(/\?format=.+$/, '?format=jpg&name=orig') :
url;
showZoomedImage(originalUrl);
return false;
}, true);
}
// 处理普通图片元素
function handleImage(img) {
if (img.dataset.zoomInitialized) return;
img.dataset.zoomInitialized = 'true';
const wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.display = 'inline-block';
img.parentNode.insertBefore(wrapper, img);
wrapper.appendChild(img);
const zoomBtn = document.createElement('div');
zoomBtn.className = 'zoom-btn';
zoomBtn.innerHTML = '+';
wrapper.appendChild(zoomBtn);
// 添加右键菜单功能
addContextMenu(img, zoomBtn);
wrapper.addEventListener('mouseenter', () => {
zoomBtn.style.display = 'flex';
});
wrapper.addEventListener('mouseleave', () => {
zoomBtn.style.display = 'none';
});
zoomBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const originalUrl = getOriginalImageUrl(img);
showZoomedImage(originalUrl);
return false;
}, true);
}
// 更新MutationObserver以使用节流
const throttle = (func, limit) => {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
const observer = new MutationObserver(
throttle((mutations) => {
initializeImages();
}, 1000) // 1秒节流
);
observer.observe(document.body, {
childList: true,
subtree: true
});
// 初始化
initializeImages();
// 添加菜单命令
GM_registerMenuCommand('清除手动设置的原图URL', () => {
GM_setValue('manualImageUrls', {});
alert('已清除所有手动设置的原图URL!');
});
// 创建放大图片的遮罩层
const overlay = document.createElement('div');
overlay.className = 'zoom-overlay';
document.body.appendChild(overlay);
function showZoomedImage(src) {
const img = document.createElement('img');
img.className = 'zoomed-img';
// 添加加载指示器
const loadingDiv = document.createElement('div');
loadingDiv.textContent = '加载中...';
loadingDiv.style.color = 'white';
overlay.innerHTML = '';
overlay.appendChild(loadingDiv);
overlay.style.display = 'flex';
// 图片加载完成后显示
img.addEventListener('load', () => {
overlay.innerHTML = '';
overlay.appendChild(img);
overlay.style.display = 'flex';
let scale = 1;
let isDragging = false;
let startX, startY;
let translateX = 0;
let translateY = 0;
// 添加滚轮缩放
img.addEventListener('wheel', (e) => {
e.preventDefault();
const delta = e.deltaY > 0 ? -0.1 : 0.1;
scale = Math.max(0.1, scale + delta);
updateTransform();
});
// 鼠标按下开始拖动
img.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX - translateX;
startY = e.clientY - translateY;
img.style.cursor = 'grabbing';
});
// 鼠标移动时更新位置
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
translateX = e.clientX - startX;
translateY = e.clientY - startY;
updateTransform();
});
// 鼠标松开停止拖动
document.addEventListener('mouseup', () => {
isDragging = false;
img.style.cursor = 'grab';
});
// 更新变换
function updateTransform() {
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
}
// 初始化鼠标样式
img.style.cursor = 'grab';
// 点击遮罩层关闭(但不包括图片)
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.style.display = 'none';
// 清除拖动相关的事件监听器
document.removeEventListener('mousemove', null);
document.removeEventListener('mouseup', null);
}
});
});
// 设置图片的src
img.src = src;
}
})();