GTAMODX
圣安地列斯
圣安地列斯
229
圣安地列斯安卓版
圣安地列斯安卓版
24
罪恶都市
罪恶都市
688
罪恶都市安卓版
罪恶都市安卓版
177
GTA3
GTA3
122
GTA3安卓版
GTA3安卓版
52
查看所有游戏
专栏QQ群
动态
登录
GTAMODX

为游戏爱好者打造的模组分享社区。发现、创造、分享,让游戏世界无限可能。

支持

  • 关于我们
  • 帮助中心
  • 用户协议
  • 隐私政策

创作者

  • 创作中心
  • 上传规范
  • 开放平台

资源

  • 全部MOD
  • 游戏下载
  • 最新资讯

平台

  • 搜索模组
  • 社区动态
  • 网站地图
  • QQ群
  • 支持MODX

友情链接

bilibiliMikiGTAOL攻略资源网罪恶都市gta吧

© 2026 GTAMODX. All rights reserved.

隐私政策服务条款
网站补全计划 - 简介点击查看大图功能实现
教程

网站补全计划 - 简介点击查看大图功能实现

Lzh10_慕黑Lzh10_慕黑 2026年8月19日 2 评论

1. 安装油猴插件

2. 打开油猴面板,添加新脚本

功能介绍

1.点击图片可以查看大图
2.图片是按钮的,长按图片即可

// ==UserScript==
// @name         Smart Image Viewer Pro
// @namespace    https://tampermonkey.net/
// @version      1.2.0
// @description  Lzh10 图片查看器:点击查看、长按查看、缩放、旋转、拖拽、画廊、下载与全屏
// @author       Lzh10
// @match        *://*/*
// @run-at       document-start
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(() => {
    'use strict';

    /*
     * 配置
     */
    const CONFIG = {
        minWidth: 100,
        minHeight: 100,

        longPressTime: 550,
        moveThreshold: 8,

        maxScale: 20,
        minScale: 0.05,
        wheelZoomSpeed: 0.0015,

        enableNormalClick: true,
        enableLongPress: true,

        showToolbar: true,
        enableDownload: true,
        enableCopy: true,
        enableGallery: true,

        ignoreSmallImages: true,

        ignoredParents: [
            'svg',
            'canvas',
            'video',
            'iframe'
        ]
    };

    /*
     * 状态
     */
    let viewer = null;
    let viewerImage = null;
    let toolbar = null;
    let info = null;

    let currentImages = [];
    let currentIndex = -1;

    let scale = 1;
    let rotation = 0;

    let translateX = 0;
    let translateY = 0;

    let dragging = false;

    let dragStartX = 0;
    let dragStartY = 0;

    let startTranslateX = 0;
    let startTranslateY = 0;

    let pointerDownX = 0;
    let pointerDownY = 0;

    let longPressTimer = null;
    let longPressTriggered = false;

    let currentSourceImage = null;
    let isOpening = false;

    /*
     * 工具函数
     */

    function isElementVisible(el) {
        if (!el || !(el instanceof Element)) {
            return false;
        }

        const rect = el.getBoundingClientRect();
        const style = getComputedStyle(el);

        return (
            rect.width > 0 &&
            rect.height > 0 &&
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            parseFloat(style.opacity || '1') > 0
        );
    }

    function isLargeEnough(img) {
        if (!CONFIG.ignoreSmallImages) {
            return true;
        }

        const rect = img.getBoundingClientRect();

        const width = Math.max(
            img.naturalWidth || 0,
            rect.width
        );

        const height = Math.max(
            img.naturalHeight || 0,
            rect.height
        );

        return (
            width >= CONFIG.minWidth &&
            height >= CONFIG.minHeight
        );
    }

    function getImageUrl(img) {
        if (!img) {
            return null;
        }

        return (
            img.currentSrc ||
            img.src ||
            img.getAttribute('data-src') ||
            img.getAttribute('data-original') ||
            img.getAttribute('data-lazy-src') ||
            img.getAttribute('data-url') ||
            null
        );
    }

    function isImageUrl(url) {
        if (!url) {
            return false;
        }

        return (
            /^data:image\//i.test(url) ||
            /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif|ico)(?:[?#].*)?$/i.test(url)
        );
    }

    function looksLikeExistingViewer(img) {
        if (!img) {
            return true;
        }

        const selectors = [
            '[data-lightbox]',
            '[data-gallery]',
            '[data-fancybox]',
            '[data-glightbox]',
            '[data-pswp]',
            '[data-photoswipe]',
            '[data-viewer]',
            '[data-image-viewer]',
            '[data-zoom]',
            '[data-zoom-image]',

            '.lightbox',
            '.light-box',
            '.fancybox',
            '.fancybox-container',
            '.fancybox-image',

            '.pswp',
            '.photoswipe',
            '.photo-swipe',

            '.glightbox',
            '.viewer',
            '.viewer-container',
            '.image-viewer',
            '.imageViewer',
            '.gallery-viewer',

            '.lightGallery',
            '.lg-container',

            '.zoomable',
            '.zoom-image',
            '.magnify',
            '.magnifier'
        ];

        try {
            for (const selector of selectors) {
                if (
                    img.matches(selector) ||
                    img.closest(selector)
                ) {
                    return true;
                }
            }
        } catch (_) {}

        let node = img;

        for (
            let i = 0;
            i < 5 && node;
            i++,
            node = node.parentElement
        ) {
            const onclick = node.getAttribute?.('onclick');

            if (onclick) {
                const text = onclick.toLowerCase();

                if (
                    text.includes('lightbox') ||
                    text.includes('fancybox') ||
                    text.includes('photoswipe') ||
                    text.includes('gallery') ||
                    text.includes('zoom') ||
                    text.includes('viewer') ||
                    text.includes('magnif')
                ) {
                    return true;
                }
            }
        }

        const link = img.closest('a');

        if (link) {
            const href = link.href;

            if (
                href &&
                (
                    isImageUrl(href) ||
                    href === getImageUrl(img)
                )
            ) {
                return true;
            }
        }

        return false;
    }

    function getClickableParent(img) {
        if (!img) {
            return null;
        }

        let node = img.parentElement;

        for (
            let i = 0;
            i < 6 && node;
            i++,
            node = node.parentElement
        ) {
            if (
                node.tagName === 'A' ||
                node.tagName === 'BUTTON' ||
                node.getAttribute('role') === 'button' ||
                node.getAttribute('role') === 'link' ||
                node.hasAttribute('onclick') ||
                node.hasAttribute('data-href') ||
                node.hasAttribute('data-url') ||
                node.tabIndex >= 0
            ) {
                return node;
            }
        }

        return null;
    }

    function shouldIgnore(img) {
        if (!(img instanceof HTMLImageElement)) {
            return true;
        }

        if (!isElementVisible(img)) {
            return true;
        }

        if (!isLargeEnough(img)) {
            return true;
        }

        const url = getImageUrl(img);

        if (!url) {
            return true;
        }

        for (const parentSelector of CONFIG.ignoredParents) {
            if (img.closest(parentSelector)) {
                return true;
            }
        }

        const role = img.getAttribute('role');

        if (role === 'presentation') {
            return true;
        }

        if (img.getAttribute('aria-hidden') === 'true') {
            return true;
        }

        const cursor = getComputedStyle(img).cursor;

        if (cursor === 'pointer') {
            if (looksLikeExistingViewer(img)) {
                return true;
            }
        }

        if (looksLikeExistingViewer(img)) {
            return true;
        }

        return false;
    }

    function collectImages() {
        if (!CONFIG.enableGallery) {
            return currentSourceImage
                ? [currentSourceImage]
                : [];
        }

        const imgs = Array.from(document.images).filter(img => {
            if (shouldIgnore(img)) {
                return false;
            }

            return !!getImageUrl(img);
        });

        const result = [];
        const seen = new Set();

        for (const img of imgs) {
            const url = getImageUrl(img);

            if (!url || seen.has(url)) {
                continue;
            }

            seen.add(url);
            result.push(img);
        }

        return result;
    }

    /*
     * 创建查看器
     */

    function createViewer() {
        if (viewer) {
            return;
        }

        viewer = document.createElement('div');
        viewer.id = 'smart-image-viewer';

        viewer.innerHTML = `
            <div class="siv-backdrop"></div>

            <div class="siv-stage">
                <img
                    class="siv-image"
                    draggable="false"
                    alt=""
                >

                <div class="siv-loading">
                    <div class="siv-spinner"></div>
                    <span>加载中…</span>
                </div>
            </div>

            <div class="siv-topbar">

                <div class="siv-title">
                    <span class="siv-counter"></span>
                </div>

                <div class="siv-actions">

                    <button
                        data-action="download"
                        title="下载"
                        aria-label="下载"
                    >
                        <svg viewBox="0 0 24 24">
                            <path d="M12 3v12"></path>
                            <path d="m7 10 5 5 5-5"></path>
                            <path d="M5 19h14"></path>
                        </svg>
                    </button>

                    <button
                        data-action="copy"
                        title="复制地址"
                        aria-label="复制地址"
                    >
                        <svg viewBox="0 0 24 24">
                            <rect
                                x="8"
                                y="8"
                                width="11"
                                height="11"
                                rx="2"
                            ></rect>
                            <path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h3"></path>
                        </svg>
                    </button>

                    <button
                        data-action="fullscreen"
                        title="全屏"
                        aria-label="全屏"
                    >
                        <svg viewBox="0 0 24 24">
                            <path d="M8 3H3v5"></path>
                            <path d="M16 3h5v5"></path>
                            <path d="M21 16v5h-5"></path>
                            <path d="M3 16v5h5"></path>
                        </svg>
                    </button>

                    <button
                        data-action="close"
                        title="关闭"
                        aria-label="关闭"
                    >
                        <svg viewBox="0 0 24 24">
                            <path d="M6 6l12 12"></path>
                            <path d="M18 6 6 18"></path>
                        </svg>
                    </button>

                </div>
            </div>

            <button
                class="siv-prev"
                data-action="prev"
                title="上一张"
                aria-label="上一张"
            >
                <svg viewBox="0 0 24 24">
                    <path d="m15 18-6-6 6-6"></path>
                </svg>
            </button>

            <button
                class="siv-next"
                data-action="next"
                title="下一张"
                aria-label="下一张"
            >
                <svg viewBox="0 0 24 24">
                    <path d="m9 18 6-6-6-6"></path>
                </svg>
            </button>

            <div class="siv-toolbar">

                <button
                    data-action="zoom-out"
                    title="缩小"
                    aria-label="缩小"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M5 12h14"></path>
                    </svg>
                </button>

                <button
                    data-action="fit"
                    title="适应窗口"
                    aria-label="适应窗口"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M4 8V5a1 1 0 0 1 1-1h3"></path>
                        <path d="M16 4h3a1 1 0 0 1 1 1v3"></path>
                        <path d="M20 16v3a1 1 0 0 1-1 1h-3"></path>
                        <path d="M8 20H5a1 1 0 0 1-1-1v-3"></path>
                    </svg>
                </button>

                <button
                    data-action="actual"
                    title="实际大小"
                    aria-label="实际大小"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M4 4h6"></path>
                        <path d="M4 4v6"></path>
                        <path d="M20 4h-6"></path>
                        <path d="M20 4v6"></path>
                        <path d="M4 20h6"></path>
                        <path d="M4 20v-6"></path>
                        <path d="M20 20h-6"></path>
                        <path d="M20 20v-6"></path>
                    </svg>
                </button>

                <button
                    data-action="zoom-in"
                    title="放大"
                    aria-label="放大"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M12 5v14"></path>
                        <path d="M5 12h14"></path>
                    </svg>
                </button>

                <span class="siv-toolbar-divider"></span>

                <button
                    data-action="rotate-left"
                    title="向左旋转"
                    aria-label="向左旋转"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M9 4 5 8l4 4"></path>
                        <path d="M5 8h7a7 7 0 1 1-6.4 10"></path>
                    </svg>
                </button>

                <button
                    data-action="rotate-right"
                    title="向右旋转"
                    aria-label="向右旋转"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="m15 4 4 4-4 4"></path>
                        <path d="M19 8h-7a7 7 0 1 0 6.4 10"></path>
                    </svg>
                </button>

                <button
                    data-action="reset"
                    title="重置"
                    aria-label="重置"
                >
                    <svg viewBox="0 0 24 24">
                        <path d="M4 12a8 8 0 1 0 2.3-5.7"></path>
                        <path d="M4 5v5h5"></path>
                    </svg>
                </button>

                <span class="siv-zoom">100%</span>

            </div>

            <div class="siv-info"></div>
        `;

        document.documentElement.appendChild(viewer);

        viewerImage = viewer.querySelector('.siv-image');
        toolbar = viewer.querySelector('.siv-toolbar');
        info = viewer.querySelector('.siv-info');

        viewer.addEventListener(
            'click',
            handleViewerClick
        );

        viewerImage.addEventListener(
            'pointerdown',
            startDrag
        );

        viewerImage.addEventListener(
            'pointermove',
            moveDrag
        );

        viewerImage.addEventListener(
            'pointerup',
            endDrag
        );

        viewerImage.addEventListener(
            'pointercancel',
            endDrag
        );

        viewer.addEventListener(
            'wheel',
            handleWheel,
            { passive: false }
        );

        viewerImage.addEventListener(
            'dblclick',
            () => {
                if (scale > 1.01) {
                    fitImage();
                } else {
                    actualSize();
                }
            }
        );

        viewerImage.addEventListener(
            'dragstart',
            e => e.preventDefault()
        );

        viewer.addEventListener(
            'transitionend',
            e => {
                if (
                    e.target === viewer &&
                    !viewer.classList.contains('siv-open')
                ) {
                    viewer.style.display = 'none';
                }
            }
        );
    }

    /*
     * CSS
     */

    function injectCSS() {
        if (document.getElementById('siv-style')) {
            return;
        }

        const style = document.createElement('style');
        style.id = 'siv-style';

        style.textContent = `
            #smart-image-viewer {
                position: fixed;
                inset: 0;
                z-index: 2147483647;
                display: none;

                font-family:
                    system-ui,
                    -apple-system,
                    BlinkMacSystemFont,
                    "Segoe UI",
                    Roboto,
                    sans-serif;

                user-select: none;

                opacity: 0;

                transition:
                    opacity
                    0.3s
                    cubic-bezier(.2,.9,.3,1);

                will-change: opacity;
            }

            #smart-image-viewer.siv-open {
                display: block;
                opacity: 1;
            }

            #smart-image-viewer.siv-closing {
                opacity: 0;
                pointer-events: none;
            }

            /*
             * 背景
             */

            .siv-backdrop {
                position: absolute;
                inset: 0;

                background:
                    rgba(0,0,0,.88);

                backdrop-filter:
                    blur(12px)
                    saturate(1.4);

                -webkit-backdrop-filter:
                    blur(12px)
                    saturate(1.4);

                transition:
                    background
                    .3s;
            }

            /*
             * 图片区域
             */

            .siv-stage {
                position: absolute;
                inset: 0;

                overflow: hidden;

                display: flex;
                align-items: center;
                justify-content: center;

                touch-action: none;
            }

            .siv-image {
                position: absolute;

                max-width: none;
                max-height: none;

                width: auto;
                height: auto;

                object-fit: contain;

                transform-origin: center center;

                will-change: transform;

                cursor: grab;

                user-select: none;
                -webkit-user-drag: none;

                transition:
                    transform
                    .25s
                    cubic-bezier(.2,.8,.2,1);

                filter:
                    drop-shadow(
                        0 8px 30px
                        rgba(0,0,0,.3)
                    );

                border-radius: 4px;
            }

            .siv-image.siv-dragging {
                cursor: grabbing;
                transition: none;
            }

            .siv-image.siv-loading-image {
                opacity: 0;

                transform:
                    scale(.96);
            }

            .siv-image.siv-loaded {
                opacity: 1;

                transform:
                    scale(1);

                transition:
                    opacity .3s,
                    transform .3s;
            }

            /*
             * 加载
             */

            .siv-loading {
                position: absolute;

                left: 50%;
                top: 50%;

                transform:
                    translate(-50%,-50%);

                display: flex;
                flex-direction: column;

                align-items: center;

                gap: 16px;

                color:
                    rgba(255,255,255,.7);

                font-size: 14px;

                letter-spacing: .3px;

                pointer-events: none;

                opacity: 0;

                transition:
                    opacity .2s;
            }

            .siv-loading.active {
                opacity: 1;
            }

            .siv-spinner {
                width: 40px;
                height: 40px;

                border-radius: 50%;

                border:
                    3px solid
                    rgba(255,255,255,.15);

                border-top-color:
                    #fff;

                animation:
                    siv-spin
                    .8s
                    cubic-bezier(.6,0,.4,1)
                    infinite;
            }

            @keyframes siv-spin {
                to {
                    transform: rotate(360deg);
                }
            }

            /*
             * 顶部
             */

            .siv-topbar {
                position: absolute;

                top: 0;
                left: 0;
                right: 0;

                height: 64px;

                display: flex;
                align-items: center;

                justify-content: space-between;

                padding: 0 20px;

                box-sizing: border-box;

                background:
                    linear-gradient(
                        to bottom,
                        rgba(0,0,0,.6),
                        transparent
                    );

                pointer-events: none;
            }

            .siv-topbar > * {
                pointer-events: auto;
            }

            .siv-title {
                color: #fff;

                font-size: 14px;
                font-weight: 400;

                text-shadow:
                    0 1px 6px
                    rgba(0,0,0,.5);

                letter-spacing: .2px;
            }

            .siv-counter {
                display: inline-flex;

                align-items: center;

                min-height: 26px;

                box-sizing: border-box;

                padding:
                    4px 12px;

                border-radius: 20px;

                background:
                    rgba(255,255,255,.12);

                backdrop-filter:
                    blur(4px);

                -webkit-backdrop-filter:
                    blur(4px);
            }

            /*
             * SVG 图标统一样式
             */

            .siv-actions button svg,
            .siv-toolbar button svg,
            .siv-prev svg,
            .siv-next svg {
                display: block;

                width: 18px;
                height: 18px;

                fill: none;

                stroke:
                    currentColor;

                stroke-width:
                    1.8;

                stroke-linecap:
                    round;

                stroke-linejoin:
                    round;

                pointer-events: none;
            }

            .siv-actions button svg {
                width: 19px;
                height: 19px;
            }

            /*
             * 顶部操作按钮
             */

            .siv-actions {
                display: flex;
                gap: 8px;
            }

            .siv-actions button,
            .siv-toolbar button {
                border: 0;
                outline: 0;

                color: #fff;

                background:
                    rgba(255,255,255,.10);

                backdrop-filter:
                    blur(6px);

                -webkit-backdrop-filter:
                    blur(6px);

                border-radius: 12px;

                min-width: 40px;
                height: 40px;

                padding:
                    0 12px;

                cursor: pointer;

                transition:
                    background .2s,
                    transform .15s,
                    box-shadow .2s;

                box-shadow:
                    0 2px 12px
                    rgba(0,0,0,.1);

                display: inline-flex;

                align-items: center;
                justify-content: center;
            }

            .siv-actions button:hover,
            .siv-toolbar button:hover {
                background:
                    rgba(255,255,255,.22);

                transform:
                    scale(1.05);

                box-shadow:
                    0 4px 16px
                    rgba(0,0,0,.2);
            }

            .siv-actions button:active,
            .siv-toolbar button:active {
                transform:
                    scale(.94);
            }

            /*
             * 上一张 / 下一张
             */

            .siv-prev,
            .siv-next {
                position: absolute;

                top: 50%;

                transform:
                    translateY(-50%);

                width: 56px;
                height: 80px;

                border: 0;
                outline: 0;

                border-radius: 16px;

                background:
                    rgba(255,255,255,.08);

                backdrop-filter:
                    blur(6px);

                -webkit-backdrop-filter:
                    blur(6px);

                color: #fff;

                cursor: pointer;

                opacity: .5;

                transition:
                    opacity .25s,
                    background .25s,
                    transform .2s;

                display: flex;

                align-items: center;
                justify-content: center;
            }

            .siv-prev:hover,
            .siv-next:hover {
                opacity: 1;

                background:
                    rgba(255,255,255,.18);

                transform:
                    translateY(-50%)
                    scale(1.04);
            }

            .siv-prev {
                left: 20px;
            }

            .siv-next {
                right: 20px;
            }

            .siv-prev svg,
            .siv-next svg {
                width: 30px;
                height: 30px;

                stroke-width: 1.7;
            }

            /*
             * 底部工具栏
             */

            .siv-toolbar {
                position: absolute;

                left: 50%;
                bottom: 28px;

                transform:
                    translateX(-50%);

                display: flex;

                align-items: center;

                gap: 6px;

                padding:
                    8px 12px;

                border-radius: 18px;

                background:
                    rgba(20,20,20,.70);

                backdrop-filter:
                    blur(20px)
                    saturate(1.6);

                -webkit-backdrop-filter:
                    blur(20px)
                    saturate(1.6);

                box-shadow:
                    0 12px 40px
                    rgba(0,0,0,.4);

                border:
                    1px solid
                    rgba(255,255,255,.06);

                flex-wrap: nowrap;

                max-width:
                    calc(100vw - 40px);

                overflow-x: auto;

                -webkit-overflow-scrolling:
                    touch;

                scrollbar-width: none;
            }

            .siv-toolbar::-webkit-scrollbar {
                display: none;
            }

            .siv-toolbar button {
                min-width: 36px;
                height: 36px;

                padding: 0 8px;

                border-radius: 10px;

                background:
                    rgba(255,255,255,.06);

                backdrop-filter: none;

                box-shadow: none;
            }

            .siv-toolbar button:hover {
                background:
                    rgba(255,255,255,.18);
            }

            .siv-toolbar-divider {
                width: 1px;
                height: 20px;

                margin:
                    0 3px;

                background:
                    rgba(255,255,255,.12);

                flex:
                    0 0 auto;
            }

            .siv-zoom {
                min-width: 52px;

                text-align: center;

                color:
                    rgba(255,255,255,.8);

                font-size: 13px;

                font-variant-numeric:
                    tabular-nums;

                letter-spacing: .2px;
            }

            /*
             * 图片信息
             */

            .siv-info {
                position: absolute;

                left: 50%;
                bottom: 96px;

                transform:
                    translateX(-50%);

                color:
                    rgba(255,255,255,.65);

                font-size: 13px;

                pointer-events: none;

                text-shadow:
                    0 2px 8px
                    rgba(0,0,0,.5);

                background:
                    rgba(0,0,0,.25);

                padding:
                    4px 16px;

                border-radius: 30px;

                backdrop-filter:
                    blur(4px);

                -webkit-backdrop-filter:
                    blur(4px);

                white-space: nowrap;

                max-width: 80vw;

                overflow: hidden;

                text-overflow: ellipsis;
            }

            /*
             * Toast
             */

            #siv-toast {
                position: fixed;

                left: 50%;
                top: 50%;

                transform:
                    translate(-50%,-50%);

                z-index:
                    2147483647;

                padding:
                    14px 28px;

                border-radius: 16px;

                background:
                    rgba(0,0,0,.78);

                backdrop-filter:
                    blur(10px);

                -webkit-backdrop-filter:
                    blur(10px);

                color: #fff;

                font-size: 15px;
                font-weight: 500;

                pointer-events: none;

                opacity: 0;

                transition:
                    opacity .25s ease;

                box-shadow:
                    0 12px 40px
                    rgba(0,0,0,.5);

                border:
                    1px solid
                    rgba(255,255,255,.06);
            }

            #siv-toast.visible {
                opacity: 1;
            }

            /*
             * 移动端
             */

            @media (max-width: 600px) {

                .siv-topbar {
                    height: 56px;
                    padding: 0 12px;
                }

                .siv-topbar button {
                    min-width: 36px;
                    height: 36px;
                }

                .siv-prev,
                .siv-next {
                    width: 44px;
                    height: 60px;
                }

                .siv-prev {
                    left: 8px;
                }

                .siv-next {
                    right: 8px;
                }

                .siv-toolbar {
                    bottom: 16px;

                    padding:
                        6px 10px;

                    gap: 4px;
                }

                .siv-toolbar button {
                    min-width: 32px;
                    height: 32px;
                }

                .siv-toolbar button svg {
                    width: 16px;
                    height: 16px;
                }

                .siv-info {
                    bottom: 72px;

                    font-size: 11px;

                    padding:
                        2px 12px;
                }
            }
        `;

        document.documentElement.appendChild(style);
    }

    /*
     * 图片变换
     */

    function updateTransform() {
        if (!viewerImage) {
            return;
        }

        viewerImage.style.transform = `
            translate3d(
                ${translateX}px,
                ${translateY}px,
                0
            )
            rotate(${rotation}deg)
            scale(${scale})
        `;

        const zoomText =
            viewer?.querySelector('.siv-zoom');

        if (zoomText) {
            zoomText.textContent =
                Math.round(scale * 100) + '%';
        }
    }

    function calculateFitScale() {
        if (
            !viewerImage?.naturalWidth ||
            !viewerImage?.naturalHeight
        ) {
            return 1;
        }

        const stage =
            viewer
                .querySelector('.siv-stage')
                .getBoundingClientRect();

        const padding = 50;

        const availableWidth =
            Math.max(
                100,
                stage.width - padding * 2
            );

        const availableHeight =
            Math.max(
                100,
                stage.height - padding * 2
            );

        const sx =
            availableWidth /
            viewerImage.naturalWidth;

        const sy =
            availableHeight /
            viewerImage.naturalHeight;

        return Math.min(sx, sy, 1);
    }

    function fitImage() {
        scale = calculateFitScale();

        rotation = 0;

        translateX = 0;
        translateY = 0;

        updateTransform();
    }

    function actualSize() {
        scale = 1;

        rotation = 0;

        translateX = 0;
        translateY = 0;

        updateTransform();
    }

    /*
     * 打开 / 关闭
     */

    function openImage(img) {
        if (!img || isOpening) {
            return;
        }

        createViewer();

        currentSourceImage = img;

        currentImages =
            collectImages();

        currentIndex =
            currentImages.indexOf(img);

        if (currentIndex < 0) {
            currentImages = [img];
            currentIndex = 0;
        }

        viewer.classList.remove(
            'siv-closing'
        );

        viewer.style.display = 'block';

        void viewer.offsetWidth;

        viewer.classList.add('siv-open');

        isOpening = true;

        document.documentElement.style.overflow =
            'hidden';

        document.body?.classList.add(
            'siv-image-viewer-open'
        );

        loadCurrentImage();

        const onOpenEnd = () => {
            viewer.removeEventListener(
                'transitionend',
                onOpenEnd
            );

            isOpening = false;
        };

        viewer.addEventListener(
            'transitionend',
            onOpenEnd
        );

        if (
            document.activeElement instanceof HTMLElement
        ) {
            document.activeElement.blur();
        }
    }

    function closeViewer() {
        if (
            !viewer ||
            !viewer.classList.contains('siv-open')
        ) {
            return;
        }

        viewer.classList.add(
            'siv-closing'
        );

        viewer.classList.remove(
            'siv-open'
        );

        document.documentElement.style.overflow =
            '';

        document.body?.classList.remove(
            'siv-image-viewer-open'
        );

        currentSourceImage = null;

        const loading =
            viewer.querySelector('.siv-loading');

        if (loading) {
            loading.classList.remove(
                'active'
            );
        }

        viewerImage.style.transition =
            'none';
    }

    /*
     * 加载图片
     */

    function loadCurrentImage() {
        const img =
            currentImages[currentIndex];

        if (!img) {
            return;
        }

        const url =
            getImageUrl(img);

        if (!url) {
            return;
        }

        const loading =
            viewer.querySelector('.siv-loading');

        loading.classList.add('active');

        viewerImage.classList.add(
            'siv-loading-image'
        );

        viewerImage.classList.remove(
            'siv-loaded'
        );

        viewerImage.style.visibility =
            'hidden';

        scale = 1;
        rotation = 0;

        translateX = 0;
        translateY = 0;

        viewerImage.onload = () => {
            loading.classList.remove(
                'active'
            );

            viewerImage.style.visibility =
                'visible';

            viewerImage.classList.remove(
                'siv-loading-image'
            );

            viewerImage.classList.add(
                'siv-loaded'
            );

            fitImage();

            updateInfo();

            setTimeout(() => {
                viewerImage.classList.remove(
                    'siv-loaded'
                );
            }, 400);
        };

        viewerImage.onerror = () => {
            loading.classList.remove(
                'active'
            );

            viewerImage.style.visibility =
                'visible';

            viewerImage.classList.remove(
                'siv-loading-image'
            );

            info.textContent =
                '图片加载失败';
        };

        viewerImage.src = url;

        updateInfo();
    }

    /*
     * 信息
     */

    function updateInfo() {
        const count =
            currentImages.length;

        const img =
            currentImages[currentIndex];

        const url =
            getImageUrl(img);

        const filename = (() => {
            try {
                return decodeURIComponent(
                    new URL(
                        url,
                        location.href
                    )
                        .pathname
                        .split('/')
                        .pop()
                );
            } catch (_) {
                return '';
            }
        })();

        const counter =
            viewer.querySelector(
                '.siv-counter'
            );

        if (counter) {
            counter.textContent =
                count > 1
                    ? `${currentIndex + 1} / ${count}`
                    : '';
        }

        if (info) {
            const dimensions =
                viewerImage.naturalWidth &&
                viewerImage.naturalHeight
                    ? `${viewerImage.naturalWidth} × ${viewerImage.naturalHeight}`
                    : '';

            info.textContent =
                `${filename || '图片'}${
                    dimensions
                        ? ' · ' + dimensions
                        : ''
                }`;
        }

        const prev =
            viewer.querySelector(
                '.siv-prev'
            );

        const next =
            viewer.querySelector(
                '.siv-next'
            );

        if (prev) {
            prev.style.display =
                count > 1
                    ? ''
                    : 'none';
        }

        if (next) {
            next.style.display =
                count > 1
                    ? ''
                    : 'none';
        }
    }

    /*
     * 图片导航
     */

    function previousImage() {
        if (currentImages.length <= 1) {
            return;
        }

        currentIndex =
            (
                currentIndex -
                1 +
                currentImages.length
            ) %
            currentImages.length;

        loadCurrentImage();
    }

    function nextImage() {
        if (currentImages.length <= 1) {
            return;
        }

        currentIndex =
            (
                currentIndex +
                1
            ) %
            currentImages.length;

        loadCurrentImage();
    }

    /*
     * 缩放
     */

    function zoomAt(
        factor,
        centerX,
        centerY
    ) {
        const oldScale =
            scale;

        const newScale =
            Math.max(
                CONFIG.minScale,
                Math.min(
                    CONFIG.maxScale,
                    scale * factor
                )
            );

        if (newScale === oldScale) {
            return;
        }

        const stage =
            viewer
                .querySelector('.siv-stage')
                .getBoundingClientRect();

        const x =
            centerX -
            (
                stage.left +
                stage.width / 2
            );

        const y =
            centerY -
            (
                stage.top +
                stage.height / 2
            );

        const ratio =
            newScale /
            oldScale;

        translateX =
            x -
            (x - translateX) *
            ratio;

        translateY =
            y -
            (y - translateY) *
            ratio;

        scale =
            newScale;

        updateTransform();
    }

    function zoomIn() {
        zoomAt(
            1.2,
            window.innerWidth / 2,
            window.innerHeight / 2
        );
    }

    function zoomOut() {
        zoomAt(
            1 / 1.2,
            window.innerWidth / 2,
            window.innerHeight / 2
        );
    }

    function handleWheel(e) {
        if (
            !viewer?.classList.contains(
                'siv-open'
            )
        ) {
            return;
        }

        e.preventDefault();

        const factor =
            Math.exp(
                -e.deltaY *
                CONFIG.wheelZoomSpeed
            );

        zoomAt(
            factor,
            e.clientX,
            e.clientY
        );
    }

    /*
     * 拖拽
     */

    function startDrag(e) {
        if (e.button !== 0) {
            return;
        }

        dragging = true;

        viewerImage.classList.add(
            'siv-dragging'
        );

        dragStartX =
            e.clientX;

        dragStartY =
            e.clientY;

        startTranslateX =
            translateX;

        startTranslateY =
            translateY;

        viewerImage.setPointerCapture?.(
            e.pointerId
        );
    }

    function moveDrag(e) {
        if (!dragging) {
            return;
        }

        translateX =
            startTranslateX +
            (
                e.clientX -
                dragStartX
            );

        translateY =
            startTranslateY +
            (
                e.clientY -
                dragStartY
            );

        updateTransform();
    }

    function endDrag(e) {
        dragging = false;

        viewerImage.classList.remove(
            'siv-dragging'
        );

        try {
            viewerImage.releasePointerCapture?.(
                e.pointerId
            );
        } catch (_) {}
    }

    /*
     * 查看器点击
     */

    function handleViewerClick(e) {
        const action =
            e.target
                .closest?.(
                    '[data-action]'
                )
                ?.dataset.action;

        if (!action) {
            if (
                e.target.classList.contains(
                    'siv-backdrop'
                )
            ) {
                closeViewer();
            }

            return;
        }

        switch (action) {

            case 'close':
                closeViewer();
                break;

            case 'prev':
                previousImage();
                break;

            case 'next':
                nextImage();
                break;

            case 'zoom-in':
                zoomIn();
                break;

            case 'zoom-out':
                zoomOut();
                break;

            case 'fit':
                fitImage();
                break;

            case 'actual':
                actualSize();
                break;

            case 'reset':
                fitImage();
                break;

            case 'rotate-left':
                rotation -= 90;
                updateTransform();
                break;

            case 'rotate-right':
                rotation += 90;
                updateTransform();
                break;

            case 'fullscreen':
                toggleFullscreen();
                break;

            case 'download':
                if (CONFIG.enableDownload) {
                    downloadCurrent();
                }
                break;

            case 'copy':
                if (CONFIG.enableCopy) {
                    copyCurrentUrl();
                }
                break;
        }
    }

    /*
     * 全屏
     */

    async function toggleFullscreen() {
        try {
            if (!document.fullscreenElement) {
                await viewer.requestFullscreen();
            } else {
                await document.exitFullscreen();
            }
        } catch (_) {}
    }

    /*
     * 下载
     */

    function downloadCurrent() {
        const url =
            getImageUrl(
                currentImages[currentIndex]
            );

        if (!url) {
            return;
        }

        const filename = (() => {
            try {
                return (
                    decodeURIComponent(
                        new URL(
                            url,
                            location.href
                        )
                            .pathname
                            .split('/')
                            .pop()
                    ) ||
                    'image'
                );
            } catch (_) {
                return 'image';
            }
        })();

        if (
            typeof GM_download ===
            'function'
        ) {
            try {
                GM_download({
                    url,
                    name: filename,
                    saveAs: false
                });

                return;
            } catch (_) {}
        }

        const a =
            document.createElement('a');

        a.href = url;
        a.download = filename;
        a.target = '_blank';

        document.body?.appendChild(a);

        a.click();

        a.remove();
    }

    /*
     * 复制地址
     */

    async function copyCurrentUrl() {
        const url =
            getImageUrl(
                currentImages[currentIndex]
            );

        if (!url) {
            return;
        }

        try {
            await navigator.clipboard.writeText(
                url
            );

            showToast(
                '图片地址已复制'
            );

        } catch (_) {

            const textarea =
                document.createElement(
                    'textarea'
                );

            textarea.value = url;

            textarea.style.position =
                'fixed';

            textarea.style.opacity =
                '0';

            document.body?.appendChild(
                textarea
            );

            textarea.select();

            try {
                document.execCommand(
                    'copy'
                );
            } catch (_) {}

            textarea.remove();

            showToast(
                '图片地址已复制'
            );
        }
    }

    /*
     * Toast
     */

    function showToast(text) {
        let toast =
            document.getElementById(
                'siv-toast'
            );

        if (!toast) {
            toast =
                document.createElement(
                    'div'
                );

            toast.id =
                'siv-toast';

            (
                document.body ||
                document.documentElement
            ).appendChild(toast);
        }

        toast.textContent = text;

        toast.classList.add(
            'visible'
        );

        clearTimeout(
            toast._timer
        );

        toast._timer =
            setTimeout(() => {
                toast.classList.remove(
                    'visible'
                );
            }, 1200);
    }

    /*
     * 普通图片点击
     */

    function handleNormalImageClick(e) {
        if (!CONFIG.enableNormalClick) {
            return;
        }

        if (
            viewer?.classList.contains(
                'siv-open'
            )
        ) {
            return;
        }

        const img =
            e.target.closest?.('img');

        if (!img) {
            return;
        }

        if (shouldIgnore(img)) {
            return;
        }

        const clickable =
            getClickableParent(img);

        if (clickable) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();

        openImage(img);
    }

    /*
     * 长按
     */

    function handlePointerDown(e) {
        if (!CONFIG.enableLongPress) {
            return;
        }

        if (e.button !== 0) {
            return;
        }

        const img =
            e.target.closest?.('img');

        if (!img) {
            return;
        }

        if (shouldIgnore(img)) {
            return;
        }

        const clickable =
            getClickableParent(img);

        if (!clickable) {
            return;
        }

        if (looksLikeExistingViewer(img)) {
            return;
        }

        pointerDownX =
            e.clientX;

        pointerDownY =
            e.clientY;

        longPressTriggered =
            false;

        clearTimeout(
            longPressTimer
        );

        longPressTimer =
            setTimeout(() => {

                longPressTriggered =
                    true;

                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                openImage(img);

            }, CONFIG.longPressTime);
    }

    function handlePointerMove(e) {
        if (!longPressTimer) {
            return;
        }

        const dx =
            e.clientX -
            pointerDownX;

        const dy =
            e.clientY -
            pointerDownY;

        if (
            Math.sqrt(
                dx * dx +
                dy * dy
            ) >
            CONFIG.moveThreshold
        ) {
            clearTimeout(
                longPressTimer
            );

            longPressTimer =
                null;
        }
    }

    function handlePointerUp() {
        if (longPressTimer) {
            clearTimeout(
                longPressTimer
            );

            longPressTimer =
                null;
        }
    }

    function handleClickAfterLongPress(e) {
        if (!longPressTriggered) {
            return;
        }

        longPressTriggered =
            false;

        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
    }

    /*
     * 键盘
     */

    function handleKeyboard(e) {
        if (
            !viewer?.classList.contains(
                'siv-open'
            )
        ) {
            return;
        }

        switch (e.key) {

            case 'Escape':
                e.preventDefault();
                closeViewer();
                break;

            case 'ArrowLeft':
                e.preventDefault();
                previousImage();
                break;

            case 'ArrowRight':
                e.preventDefault();
                nextImage();
                break;

            case '+':
            case '=':
                e.preventDefault();
                zoomIn();
                break;

            case '-':
            case '_':
                e.preventDefault();
                zoomOut();
                break;

            case '0':
                e.preventDefault();
                fitImage();
                break;

            case '1':
                e.preventDefault();
                actualSize();
                break;

            case 'r':
            case 'R':
                e.preventDefault();
                rotation += 90;
                updateTransform();
                break;

            case 'f':
            case 'F':
                e.preventDefault();
                toggleFullscreen();
                break;
        }
    }

    /*
     * 窗口变化
     */

    window.addEventListener(
        'resize',
        () => {
            if (
                viewer?.classList.contains(
                    'siv-open'
                )
            ) {
                fitImage();
            }
        }
    );

    /*
     * 初始化
     */

    function init() {
        injectCSS();
        createViewer();

        document.addEventListener(
            'click',
            handleNormalImageClick,
            true
        );

        document.addEventListener(
            'pointerdown',
            handlePointerDown,
            true
        );

        document.addEventListener(
            'pointermove',
            handlePointerMove,
            true
        );

        document.addEventListener(
            'pointerup',
            handlePointerUp,
            true
        );

        document.addEventListener(
            'pointercancel',
            handlePointerUp,
            true
        );

        document.addEventListener(
            'click',
            handleClickAfterLongPress,
            true
        );

        document.addEventListener(
            'keydown',
            handleKeyboard,
            true
        );

        window.addEventListener(
            'popstate',
            closeViewer
        );

        /*
         * document-start 时 body 可能还不存在,
         * 所以 Toast 放到 html 上即可。
         */
        const toast =
            document.createElement('div');

        toast.id =
            'siv-toast';

        (
            document.body ||
            document.documentElement
        ).appendChild(toast);
    }

    if (
        document.readyState ===
        'loading'
    ) {
        if (document.documentElement) {
            init();
        } else {
            document.addEventListener(
                'DOMContentLoaded',
                init,
                { once: true }
            );
        }
    } else {
        init();
    }

})();

#图片#网站#功能#通用#大图#点击

相关推荐

教程GTAVC - japanese.gxt 文本资源
GTAVC - japanese.gxt 文本资源
Lzh10_慕黑
39600
教程便捷的游戏文本编辑器 - GXTStudio 之使用教学
便捷的游戏文本编辑器 - GXTStudio 之使用教学
Lzh10_慕黑
42410
教程GXT - 侠盗猎车手文本资源文件
GXT - 侠盗猎车手文本资源文件
Lzh10_慕黑
15110
教程【日版】侠盗猎车手之汉化制作教学
【日版】侠盗猎车手之汉化制作教学
Lzh10_慕黑
12810
教程GTAVC - american.gxt 文本资源
GTAVC - american.gxt 文本资源
Lzh10_慕黑
56700
教程如何5分钟内开启一个VCMP服务器?
如何5分钟内开启一个VCMP服务器?
VEGETAZ
21910
教程罪恶都市安装说明+常见两大问题
罪恶都市安装说明+常见两大问题
VEGETAZ
37312
教程圣安地列斯马蹄铁、贝壳、照片收集攻略
圣安地列斯马蹄铁、贝壳、照片收集攻略
Devil
11910