Hover Zoom Revamped

Basic userscript implementation of the Imagus Hoverzoom extension

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Hover Zoom Revamped
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Basic userscript implementation of the Imagus Hoverzoom extension
// @author       TetteDev
// @match        *://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      MIT
// @run-at       document-start
// @noframes
// ==/UserScript==

const HoverImageId = `hover_image_${makeid(6)}`;
const HoverAttribute = "data-hoverable";
const NodeIsImage = (node) => { return node.tagName === "IMG" || node.nodeName === "IMG"; };

let mouseX = 0;
let mouseClientX = 0;
let mouseY = 0;
let mouseClientY = 0;
let renderAtMousePosition = false;
if (renderAtMousePosition) {
    const fnUpdateMousePosition = (e) => {
        mouseX = e.pageX;
        mouseClientX = e.clientX;
        mouseY = e.pageY;
        mouseClientY = e.clientY;
    };
    document.addEventListener('mousemove', fnUpdateMousePosition, false);
};

function containsImageDimensions(url) {
    const dimensionRegex = /(\d{2,4})x(\d{2,4})/;
    const match = url.match(dimensionRegex);

    if (match) {
        const width = parseInt(match[1], 10);
        const height = parseInt(match[2], 10);

        if (width > 0 && height > 0 && width <= 10000 && height <= 10000) {
            return {
                width: width,
                height: height
            };
        }
    }
    return null;
}
function isLazyLoaded(imgElement) {
    if (!imgElement || imgElement.tagName.toLowerCase() !== 'img') {
        return false;
    }

    if (imgElement.hasAttribute('loading') && imgElement.getAttribute('loading') === 'lazy') {
        return true;
    }

    const lazyAttributes = ['data-src', 'data-lazy-src', 'data-srcset'];
    if (lazyAttributes.some((attr) => imgElement.hasAttribute(attr))) {
        return true;
    }

    const possibleIndicatorsClassnameLazy = ["lazy", "lazyloaded"];
    if (possibleIndicatorsClassnameLazy.some((lazy) => imgElement.className.includes(lazy))) {
        return true;
    }

    // check if src might contain 'lazy' or 'lazyloaded'

    return false;
}
function makeid(length, allowedCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') {
    let result = '';
    const characters = allowedCharacters || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
      counter += 1;
    }
    return result;
}

(function() {
    'use strict';

    const fnTryQuerySelector = (node, selector, all = false) => {
        if (!node) return [];
        if (!selector) return [];
        try {
            return all ? node.querySelectorAll(selector) : node.querySelector(selector);
        } catch (err) { return []; }
    };
    const fnTryHasAttribute = (node, attributeName) => {
        if(typeof node === 'object' && node !== null && 'getAttribute' in node && node.hasAttribute(attributeName)) return true;
        else return false;
    };

    window.addEventListener('load', function () {
        document.querySelectorAll("img").forEach((node) => {
            const isImageOrContainsImage = NodeIsImage(node) || (fnTryQuerySelector(node, "img", true).length > 0);
            if (isImageOrContainsImage) {
                if (fnTryHasAttribute(node, HoverAttribute)) return;

                node.setAttribute(HoverAttribute, "true");
                node.addEventListener("mouseenter", OnElementMouseEnter); node.addEventListener("mouseleave", OnElementMouseLeave);
            }
        });
        new MutationObserver((mutationRecords, observer) => {
            mutationRecords.forEach((mutation) => {
                const addedNodes = mutation.addedNodes;
                if (addedNodes.length == 0) return;

                addedNodes.forEach((node) => {
                    if (node.id === HoverImageId) return;
                    if (fnTryHasAttribute(node, HoverAttribute)) return;

                    const isImageOrContainsImage = NodeIsImage(node) || (fnTryQuerySelector(node, "img", true).length > 0);
                    // TODO: if NodeIsImage(node) returned false but isImageOrContainsImage is still true
                    // determine if its worth making it hoverable by checking the child "img" tags in that
                    // element and confirming they meet some kind of requirements, such as
                    // bigger than some size, child images count is only 1 etc etc

                    if (isImageOrContainsImage) {
                        node.setAttribute(HoverAttribute, "true");
                        node.addEventListener("mouseenter", OnElementMouseEnter); node.addEventListener("mouseleave", OnElementMouseLeave);
                    }
                });
            });
        }).observe(document, { attributes: false, childList: true, characterData: false, subtree: true });
    });

    function OnElementMouseEnter(event) {
        OnElementMouseLeave(null);

        const targetImage = NodeIsImage(event.currentTarget) ? event.currentTarget : event.currentTarget.querySelector("img");
        let targetImageSource = "";
        if (isLazyLoaded(targetImage)) {
            let dataSetUrls = targetImage.hasAttribute("data-srcset") ? targetImage.getAttribute("data-srcset").split(',').map((url) => url.trim().split(' ')[0]) : [];
            if (dataSetUrls) {
                targetImageSource = dataSetUrls.pop();
            }
            else {
                targetImageSource = targetImage.getAttribute("data-src");
            }
        }
        else {
            targetImageSource = targetImage.src;
        }

        if (!targetImageSource) {
            targetImageSource = targetImage.getAttribute("data-src") || targetImage.src || null;

            if (!targetImageSource) {
                event.currentTarget.removeEventListener("mouseenter", OnElementMouseEnter);
                event.currentTarget.removeEventListener("mouseleave", OnElementMouseLeave);
                return;
            }
        }
        let isBase64 = targetImageSource.includes("base64");

        let width = "auto";
        let height = "auto";

        let urlDerivedImageDimensions = isBase64 ? null : containsImageDimensions(targetImageSource);
        if (urlDerivedImageDimensions) {
            width = `${urlDerivedImageDimensions.width}px`;
            height = `${urlDerivedImageDimensions.height}px`;
        } else {
            let hasWidthAttribute = targetImage.hasAttribute("width");
            if (hasWidthAttribute) width = `${targetImage.getAttribute("width")}px`;

            let hasHeightAttribute = targetImage.hasAttribute("height");
            if (hasHeightAttribute) height = `${targetImage.getAttribute("height")}px`;
        }

        // Default: render it at the center of the page
        let renderPositionX = "50%";
        let renderPositionY = "50%";
        if (renderAtMousePosition) {
            // Render at mouse
            renderPositionX = `${(mouseX)}px`;
            renderPositionY = `${(mouseY)}px`;

            // Render at mouse with some offset
            /*
            const imageWidth = parseInt(width);
            const imageHeight = parseInt(height);
            renderPositionX = (mouseClientX + (imageWidth / 2));
            if (renderPositionX + imageWidth > window.innerWidth) {
                renderPositionX = window.innerWidth - imageWidth;
            }
            renderPositionY = (mouseClientY + (imageHeight / 2));
            if (renderPositionY + imageHeight > window.innerHeight) {
                renderPositionY = window.innerHeight - imageHeight;
            }
            renderPositionX = `${renderPositionY}px`;
            renderPositionY = `${renderPositionY}px`;
            */


            // Render at mouse but use percentages relative to the page view
            /*
            const xOffsetPercentage = 20;
            const yOffsetPercentage = 25;
            const xPositionPercentage = ((mouseX/window.innerWidth)*100) + xOffsetPercentage;
            const yPositionPercentage = ((mouseY/window.innerHeight)*100) + yOffsetPercentage;

            renderPositionX = `${xPositionPercentage}%`;
            renderPositionY = `${yPositionPercentage}%`;
            */
        }

        const hover = document.createElement("img");
        hover.src = targetImageSource;
        hover.id = HoverImageId;
        hover.style.cssText = `
        margin: 0 auto;
        position: ${(renderAtMousePosition ? 'absolute' : 'fixed')};
        left: ${renderPositionX} !important;
        top: ${renderPositionY} !important;
        transform: translate(-50%, -50%);
        width: ${width} !important;
        height: ${height} !important;
        z-index: 9999;
        border: 1px dashed red;`;

        document.body.appendChild(hover);
    }

    function OnElementMouseLeave(event) {
        let hover = document.querySelector(`#${HoverImageId}`);
        if (hover) hover.remove();
    }
})();