Greasy Fork is available in English.

Zoom on Hover

Zoom on Hover enlarges images when you hover over them.

// ==UserScript==
// @name         Zoom on Hover
// @name:es      Zoom on Hover
// @version      1.6.1
// @description  Zoom on Hover enlarges images when you hover over them.
// @description:es Zoom on Hover amplía las imágenes cuando pasas el cursor sobre ellas.
// @author       Adam Jensen
// @match        *://*/*
// @grant        GM_addStyle
// @license      MIT
// @namespace https://greasyfork.org/users/1398884
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const MAX_WIDTH = 500;  // Maximum width of the enlarged image
    const MAX_HEIGHT = 500; // Maximum height of the enlarged image
    const ZOOM_FACTOR = 2;  // Zoom factor (1x, 1.5x, 2x, 3x, etc)
    const MAX_SIZE = 800;   // Maximum size in pixels (If an image has width or height greater than or equal to MAX_SIZE, it won't be enlarged)
    const MIN_SIZE = 50;    // Minimum size in pixels (Images smaller than this size won't be enlarged)
    const MAX_DISPLAY_TIME = 4;  // Maximum duration (in seconds) for how long the enlarged image stays visible

    // Styles
    GM_addStyle(`
        .ampliar-img-flotante {
            position: absolute;
            z-index: 9999;
            border: 2px solid #ccc;
            background: rgba(255, 255, 255, 0.9);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
            display: none;
            padding: 0px;
            pointer-events: none;
            height: auto;
            width: auto;
            box-sizing: border-box;
        }
        .ampliar-img-flotante img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            display: block;
            margin: 0;
            box-sizing: border-box;
        }
    `);

    // Create the floating window
    const ventanaFlotante = document.createElement('div');
    ventanaFlotante.classList.add('ampliar-img-flotante');
    const imagenFlotante = document.createElement('img');
    ventanaFlotante.appendChild(imagenFlotante);
    document.body.appendChild(ventanaFlotante);

    let timer; // To store the timeout for hiding the floating window

    // Function to enlarge the image with the zoom factor
    const ampliarImagen = (event) => {
        const target = event.target;

        let imgSrc = '';

        if (target.tagName === 'IMG') {
            // If the element is an <img>, use the src attribute
            imgSrc = target.src;

            // Skip images with "logo" or "icon" in the alt attribute
            const altText = target.alt ? target.alt.toLowerCase() : '';
            if (altText.includes('logo') || altText.includes('icon')) {
                return;
            }

        } else if (target.style.backgroundImage) {
            // If the element has a background image, extract the URL
            imgSrc = target.style.backgroundImage.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
        }

        // Skip images from unwanted sources
        if (!imgSrc || imgSrc.includes('youtube.com') || imgSrc.includes('gstatic.com')) {
            return;
        }

        // If the current image is the same as the previous one, return
        if (imagenFlotante.src === imgSrc) {
            return;
        }

        // Hide the floating window if an image is already displayed
        if (ventanaFlotante.style.display === 'block') {
            ocultarVentanaFlotante(); // Hide and reset
        }

        const img = new Image();
        img.onload = () => {
            // Don't enlarge images that are already large enough
            if (img.width >= MAX_SIZE || img.height >= MAX_SIZE) {
                return;
            }

            // Don't enlarge images that are too small
            if (img.width < MIN_SIZE || img.height < MIN_SIZE) {
                return;
            }

            const widthWithZoom = img.width * ZOOM_FACTOR;
            const heightWithZoom = img.height * ZOOM_FACTOR;

            let finalWidth, finalHeight;

            if (img.width > img.height) {
                finalWidth = Math.min(widthWithZoom, MAX_WIDTH);
                finalHeight = (img.height * finalWidth) / img.width;
                if (finalHeight > MAX_HEIGHT) {
                    finalHeight = MAX_HEIGHT;
                    finalWidth = (img.width * finalHeight) / img.height;
                }
            } else {
                finalHeight = Math.min(heightWithZoom, MAX_HEIGHT);
                finalWidth = (img.width * finalHeight) / img.height;
                if (finalWidth > MAX_WIDTH) {
                    finalWidth = MAX_WIDTH;
                    finalHeight = (img.height * finalWidth) / img.width;
                }
            }

            imagenFlotante.src = imgSrc;
            ventanaFlotante.style.display = 'block';
            ventanaFlotante.style.width = `${finalWidth}px`;
            ventanaFlotante.style.height = `${finalHeight}px`;

            // Calculate the position of the floating window
            const { top, left, width, height } = target.getBoundingClientRect();
            let newTop = top + window.scrollY;
            let newLeft = left + width + window.scrollX;

            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;

            // Adjust the position to avoid overflow to the right
            if (newLeft + ventanaFlotante.offsetWidth > viewportWidth) {
                newLeft = left - ventanaFlotante.offsetWidth + window.scrollX;
            }

            // Adjust the position to avoid overflow to the bottom
            if (newTop + ventanaFlotante.offsetHeight > viewportHeight + window.scrollY) {
                newTop = top + height + window.scrollY - ventanaFlotante.offsetHeight;
            }

            // Adjust the position to avoid overflow to the top
            if (newTop < window.scrollY) {
                newTop = window.scrollY;
            }

            ventanaFlotante.style.top = `${newTop}px`;
            ventanaFlotante.style.left = `${newLeft}px`;

            // Set a timer to automatically hide the floating window after MAX_DISPLAY_TIME
            clearTimeout(timer);  // Clear any previous timer
            timer = setTimeout(ocultarVentanaFlotante, MAX_DISPLAY_TIME * 1000);  // Hide after MAX_DISPLAY_TIME seconds
        };
        img.src = imgSrc;
    };

    // Hide the floating window and reset the image source
    const ocultarVentanaFlotante = () => {
        ventanaFlotante.style.display = 'none';
        imagenFlotante.src = '';  // Reset the image source
    };

    // Detect images
    const detectarImagenes = () => {
        const elements = document.querySelectorAll('img, [style*="background-image"]');
        elements.forEach((target) => {
            if (target.tagName === 'IMG') {
                target.addEventListener('mouseenter', ampliarImagen);
                target.addEventListener('mouseleave', ocultarVentanaFlotante);
            } else if (target.style.backgroundImage) {
                target.addEventListener('mouseenter', ampliarImagen);
                target.addEventListener('mouseleave', ocultarVentanaFlotante);
            }
        });
    };

    // Call the function to detect images
    detectarImagenes();

    // Add a mutation observer to detect dynamically added images or background elements
    const observer = new MutationObserver(detectarImagenes);
    observer.observe(document.body, { childList: true, subtree: true });
})();