Greasy Fork

Greasy Fork is available in English.

淘宝&天猫商品图片快捷复制

将商品图片拷贝到剪切板

当前为 2024-08-30 提交的版本,查看 最新版本

// ==UserScript==
// @name         淘宝&天猫商品图片快捷复制
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  将商品图片拷贝到剪切板
// @author       You
// @match        *://detail.tmall.com/item*
// @match        *://item.taobao.com/item*
// @match        *://chaoshi.detail.tmall.com/item*
// @match        *://traveldetail.fliggy.com/item*
// @match        *://detail.tmall.hk/hk/item*
// @grant        GM_setClipboard
// @license      MIT
// ==/UserScript==

(function () {
    var notificationTimer; // 通知计时器
    var isHiding = false; // 标志,表示是否正在隐藏通知

    var notification_style = document.createElement('style');
    notification_style.type = 'text/css';
    notification_style.innerHTML = `
            @keyframes showNotification {
                0% {
                    transform: translateX(-50%) scale(0);
                }
                40% {
                    transform: translateX(-50%) scale(.96);
                }
                55% {
                    transform: translateX(-50%) scale(1.04);
                }
                100% {
                    transform: translateX(-50%) scale(1);
                }
            }

            @keyframes hideNotification {
                5% {
                    transform: translateX(-50%) scale(1);
                }
                100% {                
                    opacity: 0;
                    transform: translateX(-50%) scale(0.2);
                }
            }

            @keyframes notificationButtonAnimation {
                0% { transform: translateX(-50%) scale(1); }
                100% { transform: translateX(-50%) scale(1.15); opacity: 0;}
            }

            .notification {
                cursor: default;
                position: fixed;
                bottom: 60px; 
                left: 50%;
                background-color: rgba(0, 0, 0, 0.5);
                color: #fff;
                padding: 10px;
                border-radius: 12px;
                display: none;
                z-index: 9999;
                backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */
                -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */
                transform-origin: center;
                width: auto; /* 默认宽度 */
                max-width: 68%;
                white-space: nowrap; /* 单行显示 */
                overflow: hidden; /* 超出内容隐藏 */
                text-overflow: ellipsis; /* 溢出省略号 */
                text-align: center; /* 文本居中显示 */
                transform: translateX(-50%); /* 初始水平居中 */
            }
            `;
    document.head.appendChild(notification_style);

    // 创建通知弹窗
    var NotificationContainer = document.createElement('div');
    NotificationContainer.classList.add('notification');
    NotificationContainer.id = 'showNotificationContainer';
    document.body.appendChild(NotificationContainer);

    // 添加鼠标悬浮和移出事件监听器
    NotificationContainer.addEventListener('mouseenter', function () {
        clearTimeout(notificationTimer); // 悬浮时清除计时器
        // console.log('Mouse entered notification'); // 调试日志
    });
    NotificationContainer.addEventListener('mouseleave', function () {
        // console.log('Mouse left notification'); // 调试日志
        var time = 3000;
        resetTimer(time); // 移出时重置计时器
    });

    function showNotification(message, duringTime = 3000, showImage = false) {
        // 清除之前的计时器
        if (notificationTimer) {
            clearTimeout(notificationTimer);
        }

        // 重置隐藏标志
        isHiding = false;

        // 重置通知样式
        NotificationContainer.innerHTML = '';
        NotificationContainer.style.width = 'auto';
        NotificationContainer.style.transform = 'translateX(-50%)';
        NotificationContainer.style.animation = 'none';
        NotificationContainer.style.padding = '10px';

        // 短暂移除并重新添加通知元素,强制触发动画
        document.body.removeChild(NotificationContainer);
        setTimeout(() => {
            document.body.appendChild(NotificationContainer);

            // 设置通知文本内容
            NotificationContainer.innerHTML = message;

            // 如果指定了显示图片,则读取剪贴板中的图片并显示
            if (showImage) {
                NotificationContainer.style.padding = '5px';
                navigator.clipboard.read().then(async function (data) {
                    for (const item of data) {
                        for (const type of item.types) {
                            if (type.startsWith('image/')) {
                                const blob = await item.getType(type);
                                const imageURL = URL.createObjectURL(blob);

                                const imageElement = document.createElement('img');
                                imageElement.src = imageURL;
                                imageElement.style.width = '300px';
                                imageElement.style.borderRadius = '8px';

                                const imageContainer = document.createElement('div');
                                imageContainer.style.paddingTop = '10px';
                                imageElement.style.maxWidth = 'auto';
                                imageContainer.style.borderRadius = '8px';
                                imageContainer.style.margin = 'auto';
                                imageContainer.style.display = 'block';
                                imageContainer.appendChild(imageElement);

                                NotificationContainer.appendChild(imageContainer);

                                // 图片加载完成后调整位置并设置消失定时器
                                imageElement.onload = function () {
                                    NotificationContainer.style.left = '50%';

                                    NotificationContainer.style.display = 'block';
                                    NotificationContainer.style.animation = 'showNotification 0.5s forwards';
                                    // 设置消失动画计时器
                                    resetTimer(duringTime);
                                };

                                break;
                            }
                        }
                    }
                }).catch(function (error) {
                    console.error('Error reading clipboard:', error);
                });
            } else {
                // 显示通知
                NotificationContainer.style.display = 'block';
                NotificationContainer.style.animation = 'showNotification 0.5s forwards';

                // 设置消失动画计时器
                resetTimer(duringTime);
            }
        }, 50); // 确保通知元素短暂移除再添加
    }

    function hideNotification() {
        if (isHiding) return;
        isHiding = true;

        NotificationContainer.style.animation = 'hideNotification 0.5s forwards';

        // 在动画结束后隐藏元素
        notificationTimer = setTimeout(function () {
            NotificationContainer.style.display = 'none';
            isHiding = false;
        }, 500);
    }

    function resetTimer(duringTime = 3000) {
        if (notificationTimer) {
            clearTimeout(notificationTimer);
            console.log("清除计时器");
        }
        if (duringTime > 0) {
            notificationTimer = setTimeout(function () {
                hideNotification();
                console.log("设置计时器");
            }, duringTime); // 3秒后自动隐藏通知
        }
    }

    /*
    淘宝、天猫主图复制到剪贴板功能
    */
    function createGetTmallPngButton() {
        // 找到匹配的元素的编号
        function findMatchingIndex(wrapperClass, imgClass) {
            for (let i = 0; i < wrapperClass.length; i++) {
                const wrapper = document.querySelector(wrapperClass[i]);
                if (wrapper) {
                    const img = wrapper.querySelector(imgClass[i]);
                    if (img) {
                        return i; // 返回匹配的编号
                    }
                }
            }
            return -1; // 如果没有找到匹配的元素,则返回 -1
        }

        if (!document.querySelector('#button_getTmallPngButton')) {
            const wrapperClass = ['.PicGallery--mainPicWrap--juPDFPo', '.picGallery--qY53_w0u', '.PicGallery--mainPicWrap--1c9k21r', '.item-gallery-top.item-gallery-prepub2'];
            const imgClass = ['.PicGallery--mainPic--34u4Jrw', '.mainPic--zxTtQs0P', '.PicGallery--mainPic--1eAqOie', '.item-gallery-top__img'];

            const matchingIndex = findMatchingIndex(wrapperClass, imgClass);

            if (matchingIndex !== -1) {
                const wrapper = document.querySelector(wrapperClass[matchingIndex]);
                console.log("wrapper:", wrapper);

                if (wrapper) {
                    const button = document.createElement('button');
                    button.textContent = '复制图片';
                    button.id = 'button_getTmallPngButton';
                    button.style.cssText = `
                        position: absolute;
                        top: 50%;
                        left: 50%;
                        transform: translate(-50%, -50%);
                        padding: 5px 20px;
                        font-size: 16px;
                        background-color: rgba(0, 0, 0, 0.5);
                        color: #fff;
                        border: none;
                        border-radius: 999px;
                        font-family: AlibabaPuHuiTi_2_55_Regular;
                        backdrop-filter: blur(10px) brightness(90%); /* 添加模糊效果 */
                        -webkit-backdrop-filter: blur(10px); /* 兼容Safari浏览器 */
                        text-align: center; /* 文本居中显示 */
                        cursor: pointer;
                        opacity: 0;
                        transition: opacity 0.3s ease;
                        z-index: 999;
                    `;

                    // 控制按钮显示
                    wrapper.addEventListener('mouseenter', () => {
                        button.style.opacity = '1';
                    });

                    // 控制按钮隐藏
                    wrapper.addEventListener('mouseleave', () => {
                        button.style.opacity = '0';
                    });

                    button.addEventListener('click', async () => {
                        const img = wrapper.querySelector(imgClass[matchingIndex]);
                        console.log("img:", img);
                        if (img) {
                            try {
                                const imageUrl = img.src;
                                const response = await fetch(imageUrl);
                                const blob = await response.blob();
                                const image = new Image();
                                image.src = URL.createObjectURL(blob);

                                image.onload = () => {
                                    const canvas = document.createElement('canvas');
                                    const ctx = canvas.getContext('2d');
                                    canvas.width = image.width;
                                    canvas.height = image.height;
                                    ctx.drawImage(image, 0, 0);
                                    canvas.toBlob(async (blob) => {
                                        try {
                                            await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
                                            showNotification("图片已成功复制到剪贴板", undefined, true);
                                            // alert('Image copied to clipboard!');
                                        } catch (error) {
                                            console.error('Failed to copy image to clipboard:', error);
                                            // alert('Failed to copy image to clipboard.');
                                            showNotification('图片复制失败!');
                                        }
                                    }, 'image/png');
                                };
                            } catch (error) {
                                showNotification('图片复制失败!');
                                console.error('Failed to fetch or process image:', error);
                                // alert('Failed to copy image to clipboard.');
                            }
                        } else {
                            // alert('Image not found!');
                        }
                    });

                    wrapper.style.position = 'relative'; // 确保按钮在图片上层
                    wrapper.appendChild(button);
                } else {
                    // console.error('Wrapper element not found.');
                }
            } else {
                // console.error('No matching element found.');
            }
        } else {
            // console.log('Button already exists, skipping creation.');
        }
    }

    window.addEventListener('load', createGetTmallPngButton);
    const auxiliaryFunctions = new MutationObserver((mutationsList) => {

        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                createGetTmallPngButton();
            }
        }
    });

    // 观察目标节点的子节点添加和移除
    auxiliaryFunctions.observe(document.body, { childList: true, subtree: true });
})();