Hover Zoom Minus --

popup and hover zoom pan scroll any image on any website

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

// ==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; }

})();