Greasy Fork

来自缓存

Greasy Fork is available in English.

Yipeek

"指尖轻触,万象凝于一瞥。A tap, a glimpse — the world in focus."

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Yipeek
// @name:zh-CN   一瞥
// @namespace    https://github.com/Chumor/Yipeek
// @version      1.2.0
// @description  "指尖轻触,万象凝于一瞥。A tap, a glimpse — the world in focus."
// @author       Chumor
// @match        *://*/*
// @grant        none
// @run-at       document-end
// @homepage     https://github.com/Chumor/Yipeek
// @supportURL   https://github.com/Chumor/Yipeek/issues
// ==/UserScript==

(function() {
    'use strict';

    const DEBUG_MODE = false;
    const VERSION = typeof GM_info !== 'undefined' ? GM_info.script.version : 'unknown';

    let isPreviewMode = false;
    let previewContainer = null;
    let previewImage = null;
    let imageList = [];
    let currentIndex = 0;
    let lastTap = 0;
    let currentScale = 1;
    let currentX = 0;
    let currentY = 0;
    let isDragging = false;
    let startDragX = 0;
    let startDragY = 0;
    let lastTouchDistance = 0;
    let imageInfoElement = null;
    let zoomIndicator = null;
    let containerWidth = 0;
    let containerHeight = 0;
    let originalImageWidth = 0;
    let originalImageHeight = 0;
    let imageNaturalWidth = 0;
    let imageNaturalHeight = 0;
    let bodyOverflow = '';
    let bodyPointerEvents = '';

    // 创建预览容器
    function createPreviewContainer() {
        if (previewContainer) return;

        previewContainer = document.createElement('div');
        previewContainer.id = 'image-preview-container';
        previewContainer.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0, 0, 0, 0.75);
            z-index: 999999;
            display: none;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            touch-action: none;
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            pointer-events: auto;
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
        `;

        // 关闭按钮容器
        const closeButtonContainer = document.createElement('div');
        closeButtonContainer.style.cssText = `
            position: fixed;
            top: 16px;
            right: 16px;
            width: 48px;
            height: 48px;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 2147483647;
            pointer-events: auto;
            transform: translateZ(0);
        `;
        // 关闭按钮
        const closeButton = document.createElement('div');
        closeButton.innerHTML = '×';
        closeButton.style.cssText = `
            position: relative;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            background: rgba(30, 30, 30, 0.8);
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 22px;
            cursor: pointer;
            opacity: 0.9;
            transition: all 0.2s ease;
        `;

        // 触摸反馈
        closeButton.addEventListener('touchstart', function() {
            this.style.opacity = '1';
        });

        closeButton.addEventListener('touchend', function() {
            this.style.opacity = '0.9';
        });

        // 关闭按钮点击
        closeButton.addEventListener('touchstart', function(e) {
            e.stopPropagation();
            e.preventDefault();
            closePreview();
        });

        closeButton.addEventListener('click', function(e) {
            e.stopPropagation();
            e.preventDefault();
            closePreview();
        });

        closeButtonContainer.appendChild(closeButton);
        previewContainer.appendChild(closeButtonContainer);

        // 图片容器
        const imageContainer = document.createElement('div');
        imageContainer.style.cssText = `
            position: relative;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
            height: 100%;
            overflow: visible;
            pointer-events: auto;
        `;

        // 图片信息
        imageInfoElement = document.createElement('div');
        imageInfoElement.id = 'yipeek-image-info';
        imageInfoElement.style.cssText = `
            position: absolute;
            bottom: 12px;
            left: 50%;
            transform: translateX(-50%);
            color: white;
            background: rgba(0, 0, 0, 0.6);
            padding: 6px 12px;
            border-radius: 16px;
            font-size: 13px;
            z-index: 1000;
            text-align: center;
            opacity: 0.9;
            pointer-events: none;
            max-width: 90%;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            transition: opacity 0.3s ease;
        `;

        // 缩放指示器
        zoomIndicator = document.createElement('div');
        zoomIndicator.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            color: white;
            background: rgba(0, 0, 0, 0.6);
            padding: 6px 12px;
            border-radius: 16px;
            font-size: 13px;
            z-index: 1000;
            text-align: center;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s ease;
        `;

        // 加载指示器
        const loading = document.createElement('div');
        loading.id = 'image-preview-loading';
        loading.textContent = '加载中...';
        loading.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 16px;
            padding: 10px 20px;
            background: rgba(0,0,0,0.5);
            border-radius: 12px;
            z-index: 1000;
            pointer-events: none;
        `;

        previewImage = document.createElement('img');
        previewImage.style.cssText = `
            max-width: 95%;
            max-height: 90%;
            object-fit: contain;
            user-select: none;
            display: block;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            transition: transform 0.2s ease;
            pointer-events: auto;
        `;

        imageContainer.appendChild(loading);
        imageContainer.appendChild(previewImage);
        previewContainer.appendChild(imageContainer);
        previewContainer.appendChild(imageInfoElement);
        previewContainer.appendChild(zoomIndicator);
        document.body.appendChild(previewContainer);

        // 点击背景关闭预览
        previewContainer.addEventListener('click', function(e) {
            if (e.target === previewContainer) {
                closePreview();
            }
        });

        // 阻止滚动
        previewContainer.addEventListener('touchmove', function(e) {
            if (isDragging) {
                e.preventDefault();
            }
        }, {
            passive: false
        });

        // 更新容器尺寸
        updateContainerSize();

        // 监听窗口大小变化
        window.addEventListener('resize', updateContainerSize);
    }

    // 更新容器尺寸
    function updateContainerSize() {
        if (!previewContainer) return;

        // 图片信息区域预留空间
        const infoHeight = 40;

        containerWidth = window.innerWidth * 0.95;
        containerHeight = window.innerHeight * 0.85;

        // 屏幕适配
        if (containerWidth > window.innerWidth) containerWidth = window.innerWidth;
        if (containerHeight > window.innerHeight) containerHeight = window.innerHeight;
    }

    // GtHub 适配
    function normalizeImageUrl(url) {
        if (!url) return url;

        if (url.includes('github.com') && url.includes('/blob/')) {
            return url
                .replace('github.com', 'raw.githubusercontent.com')
                .replace('/blob/', '/');
        }

        return url;
    }

    // 重置变换
    function resetTransform() {
        currentScale = 1;
        currentX = 0;
        currentY = 0;
        if (previewImage) {
            previewImage.style.transform = `translate(calc(-50% + ${currentX}px), calc(-50% + ${currentY}px)) scale(${currentScale})`;
        }
        zoomIndicator.style.opacity = '0';
    }

    // 应用变换
    function applyTransform() {
        if (previewImage) {
            previewImage.style.transform = `translate(calc(-50% + ${currentX}px), calc(-50% + ${currentY}px)) scale(${currentScale})`;
        }

        // 显示缩放比例
        if (currentScale !== 1) {
            zoomIndicator.textContent = `${Math.round(currentScale * 100)}%`;
            zoomIndicator.style.opacity = '0.9';
        } else {
            zoomIndicator.style.opacity = '0';
        }

        // 更新边界
        updateBoundary();
    }

    // 更新边界
    function updateBoundary() {
        if (!previewImage) return;

        const imgRect = previewImage.getBoundingClientRect();
        const imgWidth = imgRect.width * currentScale;
        const imgHeight = imgRect.height * currentScale;

        // 计算最大可拖动范围
        const maxX = Math.max(0, (imgWidth - containerWidth) / 2);
        const maxY = Math.max(0, (imgHeight - containerHeight) / 2);

        // 限制拖动范围
        currentX = Math.max(-maxX, Math.min(maxX, currentX));
        currentY = Math.max(-maxY, Math.min(maxY, currentY));

        if (previewImage) {
            previewImage.style.transform = `translate(calc(-50% + ${currentX}px), calc(-50% + ${currentY}px)) scale(${currentScale})`;
        }
    }

    // 获取图片标题
    function getImageTitle(img) {
        if (!img) return '图片';

        let title = img.alt || img.title || '';

        // 从src提取文件名
        if (!title && img.src) {
            const filename = img.src.split('/').pop() || '';
            title = filename.replace(/\.[^/.]+$/, '');

            // 移除参数
            title = title.split('?')[0];
            title = title.split('&')[0];

            // 简化常见文件名
            title = title.replace(/(^[\d-]+_|[\d-]+$)/g, '').trim() || '图片';
        }

        // 限制长度
        if (title.length > 25) {
            title = title.substring(0, 22) + '...';
        }

        return title;
    }

    // 更新图片信息
    function updateImageInfo() {
        if (!imageInfoElement) {
            imageInfoElement = document.getElementById('yipeek-image-info');
            if (!imageInfoElement) return;
        }

        if (currentIndex < 0 || currentIndex >= imageList.length) {
            imageInfoElement.textContent = '';
            imageInfoElement.style.opacity = '0';
            return;
        }

        try {
            const img = imageList[currentIndex];
            const title = getImageTitle(img);
            const currentNum = currentIndex + 1;
            const totalNum = imageList.length;

            imageInfoElement.textContent = `${title} ${currentNum}/${totalNum}`;
            imageInfoElement.style.opacity = '0.9';
        } catch (error) {
            console.error('Error updating image info:', error);
            if (imageInfoElement) {
                imageInfoElement.textContent = `${currentIndex + 1}/${imageList.length}`;
                imageInfoElement.style.opacity = '0.9';
            }
        }
    }

    // 预览图片
    function previewImageFn(imgElement) {
        if (!imgElement || !imgElement.src || isPreviewMode) return;

        isPreviewMode = true;

        // 确保预览容器已创建
        createPreviewContainer();

        // 保存body的原始状态
        bodyOverflow = document.body.style.overflow || '';
        bodyPointerEvents = document.body.style.pointerEvents || '';

        // 阻止body滚动和交互
        document.body.style.overflow = 'hidden';
        document.body.style.pointerEvents = 'none';

        // 收集图片
        initImageHandlers();

        // 更新当前索引
        currentIndex = imageList.indexOf(imgElement);
        if (currentIndex === -1) {
            currentIndex = 0;
            imageList = [imgElement];
        }

        // 更新图片信息
        updateImageInfo();

        // 显示加载中
        const loading = document.getElementById('image-preview-loading');
        if (loading) loading.style.display = 'block';

        // 重置状态
        resetTransform();

        // 清除旧图片源
        previewImage.src = '';

        // 加载图片
        previewImage.onload = function() {
            // 保存原始尺寸
            originalImageWidth = previewImage.naturalWidth;
            originalImageHeight = previewImage.naturalHeight;
            imageNaturalWidth = originalImageWidth;
            imageNaturalHeight = originalImageHeight;

            // 隐藏加载中
            if (loading) loading.style.display = 'none';

            // 设置初始尺寸
            setOptimalInitialSize();

            // 初始化手势
            initGestures();
        };

        previewImage.onerror = function(e) {
            console.error('Image load error:', e);
            if (loading) {
                loading.textContent = '加载失败';
                loading.style.backgroundColor = 'rgba(200,0,0,0.7)';
            }
        };

        // 加载新图片
        const rawSrc = normalizeImageUrl(imgElement.src);
        previewImage.src = rawSrc;

        // 显示预览
        if (previewContainer) {
            previewContainer.style.display = 'flex';
        }
    }

    // 初始尺寸配置
    function setOptimalInitialSize() {
        if (!previewImage || imageNaturalWidth <= 0 || imageNaturalHeight <= 0) return;

        // 计算图片的宽高比
        const imageRatio = imageNaturalWidth / imageNaturalHeight;
        const containerRatio = containerWidth / containerHeight;

        let targetScale = 1;

        // 计算最佳缩放比例
        if (imageRatio > containerRatio) {
            // 横向图片按宽度缩放,考虑信息区。
            targetScale = containerWidth / imageNaturalWidth;

            // 高度过小时适当放大
            const heightRatio = (containerHeight * 0.9) / imageNaturalHeight;
            if (heightRatio > targetScale) {
                targetScale = heightRatio;
            }
        } else {
            // 纵向图片,以高度为基准
            targetScale = containerHeight / imageNaturalHeight;
        }

        // 限制缩放范围
        const maxScale = 1.0; // 初始不放大
        const minScale = 0.6; // 最小缩放比例

        targetScale = Math.min(targetScale, maxScale);
        targetScale = Math.max(targetScale, minScale);

        // 应用缩放
        currentScale = targetScale;
        currentX = 0;
        currentY = 0;

        // 应用变换
        applyTransform();
    }

    // 初始化手势
    function initGestures() {
        if (!previewImage) return;

        // 双击放大/缩小
        previewImage.addEventListener('click', function(e) {
            e.stopPropagation();
            const now = Date.now();
            const DOUBLE_TAP_DELAY = 300;

            if (now - lastTap < DOUBLE_TAP_DELAY) {
                // 双击重置
                if (currentScale !== 1) {
                    resetTransform();
                } else {
                    // 双击放大到2倍
                    currentScale = 2;
                    applyTransform();
                }
                // 更新信息
                updateImageInfo();
            } else {
                // 单击:放大时重置
                if (currentScale !== 1) {
                    resetTransform();
                    // 更新信息
                    updateImageInfo();
                }
            }
            lastTap = now;
        });

        // 拖动和捏合缩放
        previewImage.addEventListener('touchstart', function(e) {
            if (e.touches.length === 1 && currentScale > 1) {
                isDragging = true;
                startDragX = e.touches[0].clientX - currentX;
                startDragY = e.touches[0].clientY - currentY;
                e.stopPropagation();
            } else if (e.touches.length === 2) {
                // 记录初始距离
                const dx = e.touches[0].clientX - e.touches[1].clientX;
                const dy = e.touches[0].clientY - e.touches[1].clientY;
                lastTouchDistance = Math.sqrt(dx * dx + dy * dy);
                e.stopPropagation();
            }
        }, {
            passive: false
        });

        previewImage.addEventListener('touchmove', function(e) {
            if (isDragging && e.touches.length === 1) {
                currentX = e.touches[0].clientX - startDragX;
                currentY = e.touches[0].clientY - startDragY;
                applyTransform();
                e.preventDefault();
                e.stopPropagation();
            } else if (e.touches.length === 2) {
                // 捏合缩放
                const dx = e.touches[0].clientX - e.touches[1].clientX;
                const dy = e.touches[0].clientY - e.touches[1].clientY;
                const distance = Math.sqrt(dx * dx + dy * dy);

                if (lastTouchDistance > 0) {
                    const scaleChange = distance / lastTouchDistance;
                    currentScale *= scaleChange;

                    // 限制缩放范围
                    currentScale = Math.max(0.5, Math.min(currentScale, 4));

                    applyTransform();
                    e.preventDefault();
                }

                lastTouchDistance = distance;
                e.stopPropagation();
            }
        }, {
            passive: false
        });

        previewImage.addEventListener('touchend', function(e) {
            if (isDragging) {
                isDragging = false;
            }
            if (e.touches.length < 2) {
                lastTouchDistance = 0;
            }
            updateBoundary();
        }, {
            passive: true
        });

        previewImage.addEventListener('touchcancel', function() {
            isDragging = false;
            lastTouchDistance = 0;
            updateBoundary();
        });
    }

    // 关闭预览
    function closePreview() {
        if (previewContainer) {
            previewContainer.style.display = 'none';
        }
        // 恢复body的原始状态
        document.body.style.overflow = bodyOverflow;
        document.body.style.pointerEvents = bodyPointerEvents;
        isPreviewMode = false;
        currentScale = 1;
        currentX = 0;
        currentY = 0;
        isDragging = false;
        originalImageWidth = 0;
        originalImageHeight = 0;
        imageNaturalWidth = 0;
        imageNaturalHeight = 0;
    }

    // 为所有图片添加点击事件
    function initImageHandlers() {
        // 收集所有可点击的图片
        const allImages = document.querySelectorAll('img');
        // 过滤可预览图片
        imageList = Array.from(allImages).filter(img => {
            const rect = img.getBoundingClientRect();
            if (rect.width <= 48 && rect.height <= 48) return false;
            const parent = img.parentElement;
            if (parent) {
                const cls = parent.className || '';
                // 无条件忽略
                if (/logo|kmlogo/i.test(cls)) return false;

                // 规则忽略
                const isOtherUI = /to-|goto-|go-|jump-|nav|menu|btn|icon|header|footer|aside|navbar|avatar|ad|banner|sponsor|watermark|placeholder|skeleton/i.test(cls);
                if (isOtherUI && !img.alt && !img.title) return false;
            }
            const parentLink = img.closest('a');
            if (
                (parentLink && parentLink.hasAttribute('data-preview-ignore')) ||
                img.closest('[data-yipeek-ignore]')
            ) return false;
            return true;
        });
        imageList.forEach(img => {
            if (img.dataset.yipeekBound) return;
            img.addEventListener('click', function(e) {
                // 预览功能兜底
                e.preventDefault();
                e.stopPropagation();

                const parentLink = img.closest('a');
                if (parentLink) {
                    parentLink.addEventListener('click', function(e2) {
                        e2.preventDefault();
                        e2.stopPropagation();
                    }, {
                        once: true,
                        passive: false
                    });
                }

                if (!isPreviewMode) {
                    previewImageFn(img);
                }
            }, {
                passive: false
            });
            img.dataset.yipeekBound = 'true';
        });
    }


    // 监听DOM变化
    const observer = new MutationObserver(() => {
        if (!isPreviewMode) {
            initImageHandlers();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 初始化
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initImageHandlers);
        } else {
            initImageHandlers();
        }

        // 首次预览时创建预览容器
        createPreviewContainer();
        previewContainer.style.display = 'none';
    }

    // 启动
    init();

    console.log(`Yipeek 一瞥 v${VERSION} - 指尖轻触,万象凝于一瞥`);
})();