Double Ctrl Image Zoom (Edge Style)

Double Ctrl to zoom images like Edge browser

// ==UserScript==
// @name         Double Ctrl Image Zoom (Edge Style)
// @namespace    https://products.agarmen.com
// @version      1.02
// @description  Double Ctrl to zoom images like Edge browser
// @match        *://*/*
// @grant        none
// @author       @emberasim
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    let lastCtrlTime = 0;
    const DOUBLE_PRESS_INTERVAL = 400; // ms
    let overlayDiv = null;
    let zoomedImg = null;
    let toolbar = null;

    // Track mouse position
    let mouseX = 0, mouseY = 0;
    document.addEventListener('mousemove', e => {
        mouseX = e.clientX;
        mouseY = e.clientY;
    });

    // Create overlay and controls
    function createOverlay(imgSrc) {
        disposeOverlay();

        overlayDiv = document.createElement('div');
        overlayDiv.style.position = 'fixed';
        overlayDiv.style.top = 0;
        overlayDiv.style.left = 0;
        overlayDiv.style.width = '100vw';
        overlayDiv.style.height = '100vh';
        overlayDiv.style.backgroundColor = 'rgba(0,0,0,0.85)';
        overlayDiv.style.display = 'flex';
        overlayDiv.style.alignItems = 'center';
        overlayDiv.style.justifyContent = 'center';
        overlayDiv.style.zIndex = 999999;
        overlayDiv.style.backdropFilter = 'blur(3px)';
        overlayDiv.style.opacity = '0';
        overlayDiv.style.transition = 'opacity 0.25s ease';

        // Image setup
        zoomedImg = document.createElement('img');
        zoomedImg.src = imgSrc;
        zoomedImg.style.maxWidth = '95vw';
        zoomedImg.style.maxHeight = '90vh';
        zoomedImg.style.borderRadius = '8px';
        zoomedImg.style.boxShadow = '0 0 25px rgba(0,0,0,0.7)';
        zoomedImg.style.transformOrigin = 'center center';
        zoomedImg.style.transition = 'transform 0.25s ease';
        zoomedImg.style.userSelect = 'none';
        zoomedImg.style.pointerEvents = 'none'; // let clicks pass through to overlay for closing

        overlayDiv.appendChild(zoomedImg);
        document.body.appendChild(overlayDiv);
        requestAnimationFrame(() => (overlayDiv.style.opacity = '1'));

        createToolbar(imgSrc);
        overlayDiv.appendChild(toolbar);

        overlayDiv.addEventListener('click', (e) => {
            if (e.target === overlayDiv) disposeOverlay();
        });
        document.addEventListener('keydown', escListener, { once: true });

        // Prevent background scroll and add wheel zoom
        overlayDiv.addEventListener('wheel', (e) => {
            e.preventDefault(); // stop page scroll
            e.stopPropagation();

            const delta = e.deltaY;
            if (delta < 0) adjustZoom(1.25);     // scroll up → zoom in
            else if (delta > 0) adjustZoom(1 / 1.15); // scroll down → zoom out
        }, { passive: false });
    }

    function createToolbar(imgSrc) {
        toolbar = document.createElement('div');
        toolbar.style.position = 'absolute';
        toolbar.style.bottom = '30px';
        toolbar.style.left = '50%';
        toolbar.style.transform = 'translateX(-50%)';
        toolbar.style.background = 'rgba(30,30,30,0.8)';
        toolbar.style.borderRadius = '8px';
        toolbar.style.padding = '6px 10px';
        toolbar.style.display = 'flex';
        toolbar.style.gap = '8px';
        toolbar.style.zIndex = '1000000';
        toolbar.style.fontFamily = 'sans-serif';
        toolbar.style.userSelect = 'none';
        toolbar.style.transition = 'opacity 0.25s ease';
        toolbar.style.opacity = '0';
        requestAnimationFrame(() => (toolbar.style.opacity = '1'));

        const buttons = [
            { label: '🔍+', title: 'Zoom In', action: () => adjustZoom(1.2) },
            { label: '🔍−', title: 'Zoom Out', action: () => adjustZoom(1 / 1.2) },
            { label: '⟳', title: 'Rotate', action: rotateImage },
            { label: '💾', title: 'Download', action: () => downloadImage(imgSrc) },
            { label: '↗', title: 'Open in New Tab', action: () => window.open(imgSrc, '_blank') },
            { label: '✖', title: 'Close', action: disposeOverlay }
        ];

        buttons.forEach(btn => {
            const b = document.createElement('button');
            b.textContent = btn.label;
            b.title = btn.title;
            Object.assign(b.style, {
                background: 'transparent',
                color: 'white',
                border: 'none',
                fontSize: '20px',
                cursor: 'pointer',
                padding: '4px 8px',
                borderRadius: '5px',
            });
            b.addEventListener('click', e => {
                e.stopPropagation(); // don't close overlay
                btn.action();
            });
            b.addEventListener('mouseenter', () => (b.style.background = 'rgba(255,255,255,0.15)'));
            b.addEventListener('mouseleave', () => (b.style.background = 'transparent'));
            toolbar.appendChild(b);
        });

        overlayDiv.appendChild(toolbar);
    }

    // Zoom / rotate logic
    let zoomScale = 1;
    let rotation = 0;

    function adjustZoom(factor) {
        zoomScale *= factor;
        zoomScale = Math.max(0.2, Math.min(zoomScale, 5)); // limit 0.2x–5x
        updateTransform();
    }

    function rotateImage() {
        rotation = (rotation + 90) % 360;
        updateTransform();
    }

    function updateTransform() {
        zoomedImg.style.transform = `scale(${zoomScale}) rotate(${rotation}deg)`;
    }

    function downloadImage(src) {
        const a = document.createElement('a');
        a.href = src;
        a.download = src.split('/').pop().split('?')[0] || 'image';
        document.body.appendChild(a);
        a.click();
        a.remove();
    }

    function escListener(e) {
        if (e.key === 'Escape') disposeOverlay();
    }

    function disposeOverlay() {
        if (overlayDiv) {
            overlayDiv.style.opacity = '0';
            if (toolbar) toolbar.style.opacity = '0';
            setTimeout(() => {
                overlayDiv?.remove();
                overlayDiv = null;
                zoomedImg = null;
                toolbar = null;
                zoomScale = 1;
                rotation = 0;
            }, 250);
        }
    }

    // Wait until image is ready before computing smart zoom
    const applySmartZoom = () => {
        if (!zoomedImg || !zoomedImg.complete) {
            requestAnimationFrame(applySmartZoom);
            return;
        }

        const vw = window.innerWidth * 0.9;
        const vh = window.innerHeight * 0.9;
        const iw = zoomedImg.naturalWidth;
        const ih = zoomedImg.naturalHeight;

        if (!iw || !ih) return;

        // Compute how much scaling is needed to fit in screen
        const fitScale = Math.min(vw / iw, vh / ih);

        // Only enlarge if BOTH dimensions are significantly smaller than viewport
        const isSmall = iw < vw * 0.8 && ih < vh * 0.8;

        let targetZoom;
        if (isSmall) {
            // Small image → gentle enlargement
            targetZoom = Math.min(fitScale * 1.4, 1.6);
        } else {
            // Large image → fit perfectly inside screen
            targetZoom = Math.min(fitScale, 1.0);
        }

        zoomScale = targetZoom;
        updateTransform();
    }

    // Detect double Ctrl
    window.addEventListener('keydown', (e) => {
        if (e.key === 'Control') {
            const now = Date.now();
            if (now - lastCtrlTime < DOUBLE_PRESS_INTERVAL) {
                const elems = document.elementsFromPoint(mouseX, mouseY);
                const hoveredImg = elems.find(el => el.tagName && el.tagName.toLowerCase() === 'img');
                if (hoveredImg) {
                    createOverlay(hoveredImg.src);
                    e.preventDefault();
                    applySmartZoom();
                }
            }
            lastCtrlTime = now;
        } else if (e.key === 'Escape') {
            disposeOverlay();
        }
    });
})();

//Script by #EMBER