Hover Zoom Minus --

popup and hover zoom pan scroll any image on any website

目前為 2024-03-13 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name             Hover Zoom Minus --
// @namespace        Hover Zoom Minus --
// @version          1.0
// @description      popup and hover zoom pan scroll any image on any website
// @author           Ein, with help from Copilot AI
// @match            *://*/*
// @license          MIT
// @grant            none
// ==/UserScript==

// 1. to use hover over the image(container) to view a popup of the target image
// 2. to zoom in/out use wheel up/down. to reset press the scroll button
// 3. click left mouse to lock popup this will follow the mouse, click again to release
// 4. while locked the wheel up/down will act as scroll up/down
// 5. to turn on/off hover at the bottom of the page
// 6. easy to configure

(function() {
    'use strict';

    // Configuration ----------------------------------------------------------

    // Define your list of sites you want HoverZoomMinus to run with,
    // 1st array value - 1 will default it to turn on, 0 defaults it to turn off
    // 2nd array value - "center" to always spawn it in center otherwise '' [blank] spawns near the mouse
    // 3rd array value - allowed interval between respawing of popup. ie when exited on popup but imedietly touches an img container thus making it blinks or unsuable

    const siteList = {
        /*---- reddit */ 'new.reddit.com':     [1, 'center','0'],
        /*------ 9gag */ '9gag.com':           [1, 'center', '100'],
        /*---- feedly */ 'feedly.com':         [1, 'center', '200'],
        /*----- 4chan */ 'boards.4chan.org':   [1, '', '400'],
        /* deviantart */ 'www.deviantart.com': [0, 'center', '300'],
    };

    // image container [hover area that triggers the popup]
    const imgContainers = `
        /* --- reddit */ ._3Oa0THmZ3f5iZXAQ0hBJ0k > div, ._35oEP5zLnhKEbj5BlkTBUA, ._1ti9kvv_PMZEF2phzAjsGW > div, ._28TEYBuEdOuE3kN6UyoKMa div, ._3Oa0THmZ3f5iZXAQ0hBJ0k.WjuR4W-BBrvdtABBeKUMx div,
        /* ----- 9gag */ .post-container .post-view > picture,
        /* --- feedly */ .PinableImageContainer, .entryBody,
        /* ---- 4chan */ div.post div.file a,
        /* deviantart */ ._3_LJY, ._2e1g3, ._2SlAD
    `;
    // target img
    const imgElements = `
        /* --- reddit */ ._2_tDEnGMLxpM6uOa2kaDB3, ._1dwExqTGJH2jnA-MYGkEL-,
        /* ----- 9gag */ .post-container .post-view > picture > img,
        /* --- feedly */ .pinable, .entryBody img,
        /* ---- 4chan */ div.post div.file img:nth-child(2), div.post div.file img:nth-child(1), ._3Oa0THmZ3f5iZXAQ0hBJ0k.WjuR4W-BBrvdtABBeKUMx img,
        /* deviantart */ ._3_LJY img._2e1g3 img, ._2SlAD img
    `;
    // excluded element
    const nopeElements = `
    /* ------- reddit */ ._2ED-O3JtIcOqp8iIL1G5cg
    `;

    //-------------------------------------------------------------------------


    // The HoverZoomMinus Function---------------------------------------------
    function HoverZoomMinus() {

        // Style for the popup image
        const style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = `
        .popup-image {
        max-height: calc(90vh - 10px);
        z-index: 1000;
        display: none;
        cursor: move;}`;
        document.head.appendChild(style);

        // Creating the popup image and backdrop
        const popup = document.createElement('img');
        popup.className = 'popup-image';
        document.body.appendChild(popup);
        const backdrop = document.createElement('div');
        backdrop.className = 'popup-backdrop';
        backdrop.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100 vw;
        height: 100 vh;
        display: none;`;
        document.body.appendChild(backdrop);

        //-------------------------------------------------------------------------


        // Function to show the popup
        function showPopup(src, mouseX, mouseY) {
            popup.src = src;
            popup.style.display = 'block';
            popup.style.position = 'fixed';
            popup.style.transform = 'translate(-50%, -50%)';
            backdrop.style.display = 'block';
            backdrop.style.zIndex = '999';
            backdrop.style.backdropFilter = 'blur(10px)';
            const currentDomain = window.location.hostname;
            const position = siteList[currentDomain] ? siteList[currentDomain][1] : '';
            if (position === 'center') {
                popup.style.top = '50%';
                popup.style.left = '50%';
            } else {
                popup.style.top = `${mouseY}px`;
                popup.style.left = `${mouseX}px`;
            }
        }

        //-------------------------------------------------------------------------


        // Function combined Zoom and pan
        let isPanning = false;
        let offsetX, offsetY;
        let scale = 1;
        const ZOOM_SPEED = 0.005;


        popup.addEventListener('click', function(event) {
            isPanning = !isPanning;
            if (isPanning) {
                let rect = popup.getBoundingClientRect();
                offsetX = event.clientX - rect.left - (rect.width / 2);
                offsetY = event.clientY - rect.top - (rect.height / 2);
            }
        });

        document.addEventListener('mousemove', function(event) {
            if (isPanning) {
                popup.style.left = (event.clientX - offsetX) + 'px';
                popup.style.top = (event.clientY - offsetY) + 'px';
            }
        });

        function zoomOrPan(event) {
            event.preventDefault();
            if (isPanning) {
                let deltaY = event.deltaY * -ZOOM_SPEED;
                let newTop = parseInt(popup.style.top) || 0;
                newTop += deltaY * 100;
                popup.style.top = newTop + 'px';

                offsetY -= deltaY * 100;
            } else {
                scale += event.deltaY * -ZOOM_SPEED;
                scale = Math.min(Math.max(0.125, scale), 4);
                popup.style.transform = `translate(-50%, -50%) scale(${scale})`;
            }
        }

        popup.addEventListener('wheel', zoomOrPan);

        //-------------------------------------------------------------------------


        // Function to show popup
        let popupTimer;
        document.addEventListener('mouseover', function(e) {

            if (popupTimer) return;

            let target = e.target.closest(imgContainers);
            if (!target) return;
            if (target.querySelector(nopeElements)) return;

            let currentContainer = target;
            const imageElement = currentContainer.querySelector(imgElements);
            if (imageElement) {
                const currentDomain = window.location.hostname;
                const domainConfig = siteList[currentDomain];
                const position = siteList[currentDomain] ? siteList[currentDomain][1] : '';
                const delayTimer = domainConfig ? domainConfig[2] : '';

                if (delayTimer === '') {
                    if (position === 'center') {
                        showPopup(imageElement.src);
                    } else {
                        showPopup(imageElement.src, e.clientX, e.clientY);
                    }
                } else {
                    popupTimer = setTimeout(() => {
                        if (position === 'center') {
                            showPopup(imageElement.src);
                        } else {
                            showPopup(imageElement.src, e.clientX, e.clientY);
                        }
                        popupTimer = null;
                    }, parseInt(delayTimer));
                }
            }
        });

        //-------------------------------------------------------------------------


        // Function to hide popup

        function hidePopup() {
            if (popupTimer) {
                clearTimeout(popupTimer);
            }
            document.body.appendChild(backdrop);
            popup.style.display = 'none';
            popup.style.left = '50%';
            popup.style.top = '50%';
            popup.style.position = 'fixed';
            popup.style.transform = 'translate(-50%, -50%) scale(1)';
            backdrop.style.zIndex = '';
            backdrop.style.display = 'none';
            backdrop.style.backdropFilter = '';
            isPanning = false;
        }

        document.addEventListener('mouseout', function(e) {
            let target = e.target.closest('.imgContainers');
            let relatedTarget = e.relatedTarget;

            if (target && (!relatedTarget || (!relatedTarget.matches('.popup-image') && !relatedTarget.closest('.imgContainers')))) {
                hidePopup();
                const currentDomain = window.location.hostname;
                const delayTimer = siteList[currentDomain] ? siteList[currentDomain][2] : '';

                if (delayTimer !== '') {
                    popupTimer = setTimeout(() => {
                        popupTimer = null;
                    }, parseInt(delayTimer));
                }
            }
        });

        popup.addEventListener('mouseout', function() {
            hidePopup();
            const currentDomain = window.location.hostname;
            const delayTimer = siteList[currentDomain] ? siteList[currentDomain][2] : '';

            if (delayTimer !== '') {
                popupTimer = setTimeout(() => {
                    popupTimer = null;
                }, parseInt(delayTimer));
            }
        });

        document.addEventListener('keydown', function(event) {
            if (event.key === "Escape") {
                event.preventDefault();
                hidePopup();
            }
        });
    }

    //-------------------------------------------------------------------------


    // Is HoverZoomMinus to be run
    const indicatorBar = document.createElement('div');
    indicatorBar.style.cssText = `
    position: fixed;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9999;
    height: 30px;
    width: 50vw;
    background: #0000;`;
    document.body.appendChild(indicatorBar);

    function toggleIndicator() {
        indicatorValue = 1 - indicatorValue;
        indicatorBar.style.background = indicatorValue ? 'rgba(50, 190, 152, 0.5)' : 'rgba(174, 0, 1, 0.5)';
        setTimeout(() => {
            indicatorBar.style.background = '#0000';
        }, 1000);
        if (indicatorValue === 1) {
            HoverZoomMinus();
        } else {
            const existingPopup = document.body.querySelector('.popup-image');
            if (existingPopup) document.body.removeChild(existingPopup);
            const existingBackdrop = document.body.querySelector('.popup-backdrop');
            if (existingBackdrop) document.body.removeChild(existingBackdrop);
        }
    }

    let indicatorValue;
    const currentDomain = window.location.hostname;
    if (siteList.hasOwnProperty(currentDomain)) {
        indicatorValue = siteList[currentDomain][0];

        let hoverTimeout;
        indicatorBar.addEventListener('mouseenter', () => {
            hoverTimeout = setTimeout(toggleIndicator, 500);
        });
        indicatorBar.addEventListener('mouseleave', () => {
            clearTimeout(hoverTimeout);
            indicatorBar.style.background = '#0000';
        });
        if (indicatorValue === 1) {
            HoverZoomMinus();
        }

    } else { return; }

})();