Greasy Fork

Greasy Fork is available in English.

GIF 重播按钮

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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);
})();