您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
禁用淘宝、天猫、1688等电商平台的js-image-zoom效果,方便右键保存图片。
当前为
// ==UserScript== // @name 禁用电商平台图片缩放效果 // @name:en Disable E-commerce Image Zoom Effect // @namespace http://greasyfork.icu/users/3001-hanjian-wu // @version 1.3.1 // @description 禁用淘宝、天猫、1688等电商平台的js-image-zoom效果,方便右键保存图片。 // @description:en Disable js-image-zoom effect on Taobao, Tmall, 1688 and other e-commerce platforms for easy right-click image saving. Fully fixed right-click direct access to images // @author hanjian wu // @homepage http://greasyfork.icu/users/3001-hanjian-wu // @supportURL http://greasyfork.icu/users/3001-hanjian-wu // @license MIT // @match https://*.taobao.com/item.htm* // @match https://*.tmall.com/item.htm* // @match https://*.1688.com/offer/*.html* // @match https://item.taobao.com/* // @match https://detail.tmall.com/* // @match https://detail.1688.com/* // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iIzAwN0FGRiIvPgo8cGF0aCBkPSJNMTYgMTZIMzJWMzJIMTZWMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzYgMTZINDhWMjhIMzZWMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTYgMzZIMjhWNDhIMTZWMzZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzIgMzJINDhWNDhIMzJWMzJaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjciLz4KPC9zdmc+ // @grant none // @run-at document-start // @noframes // @compatible chrome // @compatible firefox // @compatible edge // @compatible safari // ==/UserScript== (function() { 'use strict'; console.log('电商图片缩放禁用脚本已启动 v1.3.1 (修复版)'); // 检测当前平台 const is1688 = window.location.hostname.includes('1688.com'); // const isTaobao = window.location.hostname.includes('taobao.com'); // 已被 isTaobaoTmallCommon 替代 // const isTmall = window.location.hostname.includes('tmall.com'); // 已被 isTaobaoTmallCommon 替代 const isTaobaoTmallCommon = window.location.hostname.includes('taobao.com') || window.location.hostname.includes('tmall.com'); let zoomEventBlocked = false; // 禁用图片缩放的主要函数 function disableImageZoom() { if (is1688) { handle1688Zoom(); } else if (isTaobaoTmallCommon) { // 使用合并后的检测 handleTaobaoTmallZoom(); } } // 处理1688的缩放 function handle1688Zoom() { const zoomElements = document.querySelectorAll('.scale-img, .scaled-img'); zoomElements.forEach(el => { if (el.style.display !== 'none') { el.style.display = 'none !important'; el.style.visibility = 'hidden !important'; el.style.opacity = '0 !important'; el.style.pointerEvents = 'none !important'; } }); // 确保主图片可以右键 const previewImg = document.querySelector('.detail-gallery-preview .preview-img'); if (previewImg) { enableImageRightClick(previewImg); } } // 处理淘宝/天猫的缩放 - 重点修复右键问题 function handleTaobaoTmallZoom() { // 隐藏缩放相关元素 const zoomElements = document.querySelectorAll('.js-image-zoom__zoomed-area, .js-image-zoom__zoomed-image, #lensDiv'); zoomElements.forEach(el => { el.style.display = 'none !important'; el.style.visibility = 'hidden !important'; el.style.opacity = '0 !important'; el.style.pointerEvents = 'none !important'; }); // 关键修复:让容器的鼠标事件穿透到图片 // 使用属性选择器 [class*="--mainPicWrap--"] 来匹配动态类名 const mainPicWrappers = document.querySelectorAll('[class*="--mainPicWrap--"]'); mainPicWrappers.forEach(wrapper => { // 让容器的鼠标事件穿透 wrapper.style.pointerEvents = 'none'; // 找到其中的图片并确保可以接收鼠标事件 // 使用属性选择器 [class*="--mainPic--"] const mainPic = wrapper.querySelector('[class*="--mainPic--"]'); if (mainPic) { // 让图片重新获得鼠标事件 mainPic.style.pointerEvents = 'auto'; mainPic.style.position = 'relative'; // 确保 z-index 生效 mainPic.style.zIndex = '999'; // 提升层级 enableImageRightClick(mainPic); console.log('已修复图片右键功能:', mainPic.src); } }); // 确保缩略图可以点击切换 // 使用属性选择器 [class*="--thumbnail--"] 和 [class*="--thumbnailPic--"] const thumbnails = document.querySelectorAll('[class*="--thumbnail--"]'); thumbnails.forEach(thumb => { thumb.style.pointerEvents = 'auto'; const thumbImg = thumb.querySelector('[class*="--thumbnailPic--"]'); if (thumbImg) { thumbImg.style.pointerEvents = 'auto'; } }); // 确保切换标签可以点击 // 使用属性选择器 [class*="--switchTabsItem--"] const switchTabs = document.querySelectorAll('[class*="--switchTabsItem--"]'); switchTabs.forEach(tab => { tab.style.pointerEvents = 'auto'; }); } // 启用图片右键功能 function enableImageRightClick(img) { if (!img) return; // 确保图片可以接收鼠标事件 img.style.pointerEvents = 'auto'; img.style.userSelect = 'auto'; img.style.webkitUserSelect = 'auto'; img.style.mozUserSelect = 'auto'; img.style.msUserSelect = 'auto'; img.style.webkitTouchCallout = 'default'; // 尝试恢复iOS长按菜单 // 移除所有可能阻止右键的事件处理器 img.oncontextmenu = null; img.ondragstart = null; img.onselectstart = null; // 保留 mousedown 和 mouseup 以免影响其他功能,但确保右键可以穿透 // img.onmousedown = null; // img.onmouseup = null; // 移除事件监听器(如果存在) const events = ['contextmenu', 'dragstart', 'selectstart' /*, 'mousedown', 'mouseup'*/]; // 谨慎移除 mousedown/up events.forEach(eventType => { img.removeEventListener(eventType, preventDefault, true); // 移除捕获阶段 img.removeEventListener(eventType, preventDefault, false); // 移除冒泡阶段 }); // 添加强制允许右键的事件监听器 // 使用捕获阶段确保优先处理 img.addEventListener('contextmenu', function(e) { e.stopPropagation(); // 阻止事件冒泡到父元素,以防父元素有阻止右键的逻辑 console.log('图片右键菜单已启用 (脚本拦截)'); return true; // 明确允许默认行为 }, true); // 使用捕获阶段 img.addEventListener('mousedown', function(e) { if (e.button === 2) { // 右键 e.stopPropagation(); // 阻止事件冒泡 console.log('图片右键点击已启用 (脚本拦截)'); // return true; // mousedown 通常不需要 return true 来允许 contextmenu } }, true); // 使用捕获阶段 } // 阻止事件的通用函数 (如果需要) function preventDefault(e) { e.preventDefault(); e.stopPropagation(); // return false; // 在事件监听器中 return false 等同于 preventDefault + stopPropagation } // 精准的事件拦截 function blockZoomEvents() { if (zoomEventBlocked) return; zoomEventBlocked = true; // 拦截可能的缩放函数 if (window.ImageZoom) { window.ImageZoom = function() { console.log('ImageZoom 被禁用 (脚本重写)'); return {}; }; // 返回一个对象模拟 } // 尝试覆盖可能的jQuery插件 if (typeof jQuery !== 'undefined' && jQuery.fn.imagezoom) { jQuery.fn.imagezoom = function() { console.log('jQuery.fn.imagezoom 被禁用'); return this; }; } // 拦截容器上的缩放事件,但保留其他功能 const originalAddEventListener = Element.prototype.addEventListener; Element.prototype.addEventListener = function(type, listener, options) { let isMainPicContainerTarget = false; if (this.classList) { for (const cls of this.classList) { if (cls.includes('--mainPicWrap--')) { // 使用部分匹配 isMainPicContainerTarget = true; break; } } } const isZoomMouseEvent = (type === 'mousemove' || type === 'mouseover' || type === 'mouseenter' || type === 'mouseleave' || type === 'mouseout'); if (isMainPicContainerTarget && isZoomMouseEvent) { const listenerStr = listener.toString(); // 更宽松地匹配可能与缩放相关的事件处理器 if (listenerStr.includes('zoom') || listenerStr.includes('scale') || listenerStr.includes('lens') || listenerStr.includes('magnif')) { console.log(`拦截了容器 [${Array.from(this.classList).join(', ')}] 的缩放事件: ${type}`); return; // 阻止添加此事件监听器 } } // 对于1688 (保持原有逻辑) const is1688ZoomElement = this.classList && (this.classList.contains('scale-img') || this.classList.contains('scaled-img')); if (is1688ZoomElement) { console.log(`拦截了1688缩放元素 [${Array.from(this.classList).join(', ')}] 的事件: ${type}`); return; } return originalAddEventListener.call(this, type, listener, options); }; } // 添加CSS样式 function addZoomDisableStyles() { const style = document.createElement('style'); style.id = 'zoom-disable-styles'; let cssText = ''; if (is1688) { cssText = ` /* 1688 缩放元素禁用 */ .detail-gallery-preview .scale-img, .detail-gallery-preview .scaled-img { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } /* 确保1688图片可以右键 */ .detail-gallery-preview .preview-img { pointer-events: auto !important; user-select: auto !important; -webkit-user-select: auto !important; position: relative !important; /* 确保 z-index 生效 */ z-index: 998 !important; /* 比下面的主图低一级,但高于干扰元素 */ } `; } else if (isTaobaoTmallCommon) { // 使用合并后的检测 cssText = ` /* 淘宝天猫缩放元素禁用 */ .js-image-zoom__zoomed-area, .js-image-zoom__zoomed-image, #lensDiv { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } /* 关键修复:让容器鼠标事件穿透,图片获得最高优先级 */ /* 使用属性选择器 [class*="--mainPicWrap--"] */ [class*="--mainPicWrap--"] { pointer-events: none !important; /* 容器不接收鼠标事件 */ } /* 让主图片重新获得鼠标事件和最高层级 */ /* 使用属性选择器 [class*="--mainPic--"] */ [class*="--mainPic--"] { pointer-events: auto !important; /* 图片接收鼠标事件 */ user-select: auto !important; -webkit-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important; position: relative !important; /* 确保 z-index 生效 */ z-index: 999 !important; /* 提升图片层级 */ -webkit-touch-callout: default !important; -khtml-user-select: auto !important; /* 针对旧版KHTML */ } /* 确保缩略图可以点击 */ [class*="--thumbnail--"], [class*="--thumbnailPic--"] { pointer-events: auto !important; z-index: 99 !important; /* 确保缩略图在某些情况下可点 */ } /* 确保切换标签可以点击 */ [class*="--switchTabsItem--"] { pointer-events: auto !important; z-index: 99 !important; } /* 确保缩略图容器可以交互 */ [class*="--thumbnailsWrap--"], [class*="--thumbnails--"] { /* 通常 thumbnails 是 wrap 内部的列表 */ pointer-events: auto !important; } /* 确保底部切换标签容器可以交互 */ [class*="--bottomSwitchTabsWrap--"], [class*="--switchTabsWrap--"] { pointer-events: auto !important; } `; } style.textContent = cssText; if (!document.getElementById('zoom-disable-styles')) { (document.head || document.documentElement).appendChild(style); } else { // 如果样式已存在,则更新其内容 document.getElementById('zoom-disable-styles').textContent = cssText; } } // 强制移除右键阻止 function forceEnableRightClickOnDocument() { // 移除可能的全局右键阻止 document.oncontextmenu = null; document.onselectstart = null; document.ondragstart = null; // 移除body上的右键阻止 if (document.body) { document.body.oncontextmenu = null; document.body.onselectstart = null; document.body.ondragstart = null; } // 尝试移除document上的事件监听 const eventsToClear = ['contextmenu', 'selectstart', 'dragstart']; eventsToClear.forEach(eventType => { // Note: This won't remove listeners added with addEventListener directly without a reference. // This is more of a "best effort" for listeners assigned via on<eventtype>. // For addEventListener, specific targeting or overriding prototypes is needed (which is partially done in blockZoomEvents) }); } // 监控动态变化 function setupObserver() { const observer = new MutationObserver(function(mutations) { let needsReapply = false; mutations.forEach(function(mutation) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { // Element node // 检查是否是主图容器或主图本身,或者其他相关元素 if ( (node.matches && (node.matches('[class*="--mainPicWrap--"]') || node.matches('[class*="--mainPic--"]'))) || (node.querySelector && (node.querySelector('[class*="--mainPicWrap--"]') || node.querySelector('[class*="--mainPic--"]'))) ) { needsReapply = true; } // 针对1688 if (is1688 && ((node.matches && (node.matches('.scale-img') || node.matches('.scaled-img'))) || (node.querySelector && (node.querySelector('.scale-img') || node.querySelector('.scaled-img')))) ) { needsReapply = true; } // 针对通用缩放层 if ( (node.matches && (node.matches('.js-image-zoom__zoomed-area') || node.matches('.js-image-zoom__zoomed-image') || node.id === 'lensDiv')) || (node.querySelector && (node.querySelector('.js-image-zoom__zoomed-area') || node.querySelector('.js-image-zoom__zoomed-image') || node.querySelector('#lensDiv'))) ) { needsReapply = true; } } }); } else if (mutation.type === 'attributes') { // 如果某些关键元素的 class 或 style 变化,也可能需要重新应用 if (mutation.target.matches && (mutation.target.matches('[class*="--mainPicWrap--"]') || mutation.target.matches('[class*="--mainPic--"]'))) { needsReapply = true; } } }); if (needsReapply) { console.log('DOM变化,重新应用缩放禁用和右键修复'); disableImageZoom(); // This will call either handle1688Zoom or handleTaobaoTmallZoom // forceEnableRightClickOnDocument(); // 通常 enableImageRightClick 作用于特定图片元素已经足够 } }); observer.observe(document.documentElement || document.body, { // Observe the whole document if body isn't ready childList: true, subtree: true, attributes: true, // 监听属性变化,以防样式被动态修改 attributeFilter: ['class', 'style'] // 只关心class和style属性 }); console.log('MutationObserver已设置'); } // 初始化函数 function init() { console.log('初始化电商图片缩放禁用脚本 (修复版)...'); blockZoomEvents(); // 应该尽早执行,在页面脚本运行前尝试Hook addZoomDisableStyles(); // 注入基础CSS disableImageZoom(); // 首次尝试处理 forceEnableRightClickOnDocument(); // 尝试清除全局右键阻止 // DOMContentLoaded 后再次确保,因为 blockZoomEvents 修改了原型,可能需要页面元素存在 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { console.log('DOMContentLoaded: 应用修复'); addZoomDisableStyles(); // 确保样式在DOM加载后应用 disableImageZoom(); forceEnableRightClickOnDocument(); setupObserver(); // 在DOM加载完毕后启动观察者 }); } else { // 如果 'loading' 阶段已过 (interactive or complete) console.log('Document already loaded: 应用修复并设置观察者'); addZoomDisableStyles(); disableImageZoom(); forceEnableRightClickOnDocument(); setupObserver(); } // 延迟执行,应对某些异步加载的组件 setTimeout(() => { console.log('延迟修复 (1s)'); addZoomDisableStyles(); // 确保样式 disableImageZoom(); forceEnableRightClickOnDocument(); }, 1000); setTimeout(() => { console.log('二次延迟修复 (3s)'); addZoomDisableStyles(); // 确保样式 disableImageZoom(); forceEnableRightClickOnDocument(); }, 3000); } // @run-at document-start 意味着脚本会很早执行 // 因此,init() 中的 DOMContentLoaded 监听器和直接执行路径都很重要 init(); // 调用初始化 // 定期维护 (作为最后的保障,但 MutationObserver 应该是主要手段) const maintenanceInterval = setInterval(() => { // console.log('定期维护检查'); disableImageZoom(); // forceEnableRightClickOnDocument(); // 这个可能过于频繁,主要依赖 enableImageRightClick }, 5000); // 页面卸载时清理 window.addEventListener('unload', () => { if (maintenanceInterval) { clearInterval(maintenanceInterval); } // 理论上 MutationObserver 会自动停止,但也可以显式disconnect }); console.log('电商图片缩放禁用脚本加载完成 (修复版) - 专门修复右键问题'); })();