GIF 重播按钮

为页面上的 GIF 图片添加重播按钮

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GIF 重播按钮
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  为页面上的 GIF 图片添加重播按钮
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 已处理的 GIF 图片集合
    const processedGifs = new WeakSet();

    // 添加样式
    const style = document.createElement('style');
    style.textContent = `
        .gif-replay-container {
            position: absolute;
            top: 0;
            right: 0;
            width: 60px;
            height: 60px;
            pointer-events: none;
            z-index: 9999;
        }

        .gif-replay-button {
            position: absolute;
            top: 8px;
            right: 8px;
            width: 32px;
            height: 32px;
            background: rgba(0, 0, 0, 0.6);
            border: none;
            border-radius: 50%;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            transition: opacity 0.2s, background 0.2s;
            pointer-events: auto;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        }

        .gif-replay-button:hover {
            background: rgba(0, 0, 0, 0.8);
        }

        .gif-replay-container:hover .gif-replay-button {
            opacity: 1;
        }

        .gif-replay-button::before {
            content: '';
            width: 0;
            height: 0;
            border-style: solid;
            border-width: 6px 0 6px 10px;
            border-color: transparent transparent transparent #fff;
            margin-left: 2px;
        }

        .gif-replay-button.replaying::before {
            border-width: 8px 0 8px 8px;
            animation: spin 0.5s linear;
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        /* 确保包含 GIF 的容器有相对定位 */
        .gif-wrapper {
            position: relative;
            display: inline-block;
        }
    `;
    document.head.appendChild(style);

    // 检查是否是 GIF 图片
    function isGif(img) {
        if (!img || !img.src) return false;
        
        // 检查 URL 是否包含 .gif
        const url = img.src.toLowerCase();
        if (url.includes('.gif')) return true;
        
        // 有些 GIF 可能通过 data URL 或其他方式加载,这里不做检测
        return false;
    }

    // 为 GIF 添加重播按钮
    function addReplayButton(img) {
        // 如果已经处理过,跳过
        if (processedGifs.has(img)) return;
        processedGifs.add(img);

        // 确保图片的父元素有相对定位
        let wrapper = img.parentElement;
        const computedStyle = window.getComputedStyle(wrapper);
        
        // 如果父元素没有定位属性,创建一个 wrapper
        if (computedStyle.position === 'static') {
            const newWrapper = document.createElement('div');
            newWrapper.className = 'gif-wrapper';
            img.parentNode.insertBefore(newWrapper, img);
            newWrapper.appendChild(img);
            wrapper = newWrapper;
        }

        // 创建按钮容器
        const container = document.createElement('div');
        container.className = 'gif-replay-container';

        // 创建重播按钮
        const button = document.createElement('button');
        button.className = 'gif-replay-button';
        button.title = '重新播放 GIF';
        
        // 点击事件 - 重新加载 GIF
        button.addEventListener('click', (e) => {
            e.stopPropagation();
            
            // 添加动画效果
            button.classList.add('replaying');
            setTimeout(() => button.classList.remove('replaying'), 500);

            // 重新加载 GIF(通过修改 src 添加时间戳)
            const originalSrc = img.src.split('?')[0]; // 移除现有的查询参数
            const timestamp = new Date().getTime();
            img.src = `${originalSrc}?replay=${timestamp}`;
        });

        container.appendChild(button);
        
        // 将容器添加到图片的父元素
        wrapper.style.position = wrapper.style.position || 'relative';
        wrapper.appendChild(container);
    }

    // 处理页面上所有的 GIF
    function processGifs() {
        const images = document.getElementsByTagName('img');
        for (let img of images) {
            if (isGif(img) && img.complete && img.naturalWidth > 0) {
                addReplayButton(img);
            } else if (isGif(img)) {
                // 如果图片还没加载完,等待加载完成
                img.addEventListener('load', () => addReplayButton(img), { once: true });
            }
        }
    }

    // 监听 DOM 变化,处理动态添加的 GIF
    const observer = new MutationObserver((mutations) => {
        for (let mutation of mutations) {
            for (let node of mutation.addedNodes) {
                if (node.nodeType === 1) { // 元素节点
                    if (node.tagName === 'IMG' && isGif(node)) {
                        if (node.complete && node.naturalWidth > 0) {
                            addReplayButton(node);
                        } else {
                            node.addEventListener('load', () => addReplayButton(node), { once: true });
                        }
                    } else {
                        // 检查新添加节点内的所有图片
                        const imgs = node.getElementsByTagName?.('img') || [];
                        for (let img of imgs) {
                            if (isGif(img)) {
                                if (img.complete && img.naturalWidth > 0) {
                                    addReplayButton(img);
                                } else {
                                    img.addEventListener('load', () => addReplayButton(img), { once: true });
                                }
                            }
                        }
                    }
                }
            }
        }
    });

    // 启动监听
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 初始处理
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', processGifs);
    } else {
        processGifs();
    }

    // 页面完全加载后再处理一次
    window.addEventListener('load', processGifs);
})();