Overlay Custom Scrollbar

Custom X and Y scrollbars with drag-to-seek, visible on hover and scroll, no width impact, cross-browser support

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name Overlay Custom Scrollbar
// @namespace http://tampermonkey.net/
// @version 2.2
// @description Custom X and Y scrollbars with drag-to-seek, visible on hover and scroll, no width impact, cross-browser support
// @license MIT
// @author Grok and Claude
// @match *://*/*
// @grant none
// ==/UserScript==

(function() {
    'use strict';

    // Add CSS styles
    const style = document.createElement('style');
    style.textContent = `
        /* Hide native scrollbars */
        html {
            -ms-overflow-style: none !important; /* Edge/IE */
            scrollbar-width: none !important; /* Firefox */
            overflow: scroll; /* Enable X and Y scrolling */
        }

        html::-webkit-scrollbar, body::-webkit-scrollbar {
            display: none !important; /* Chrome/Edge/Safari */
            width: 0px !important;
            height: 0px !important;
        }

        /* Y-axis custom scrollbar container */
        .custom-scrollbar-y {
            position: fixed;
            top: 0;
            right: 0;
            width: 8px;
            height: 100%;
            opacity: 0;
            transition: opacity 0.2s ease;
            z-index: 9999;
            background: rgba(0, 0, 0, 0.05);
        }

        /* X-axis custom scrollbar container */
        .custom-scrollbar-x {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 8px;
            opacity: 0;
            transition: opacity 0.2s ease;
            z-index: 9999;
            background: rgba(0, 0, 0, 0.05);
        }

        /* Custom scrollbar thumb */
        .custom-scrollbar-thumb {
            position: absolute;
            background: #888;
            border-radius: 4px;
            opacity: 1; /* Always visible */
            cursor: pointer;
        }

        .custom-scrollbar-y .custom-scrollbar-thumb {
            width: 100%;
        }

        .custom-scrollbar-x .custom-scrollbar-thumb {
            height: 100%;
        }

        .custom-scrollbar-thumb:hover {
            background: #555;
        }

        /* Show during scrolling or dragging */
        html.scrolling .custom-scrollbar-y,
        html.scrolling .custom-scrollbar-x {
            opacity: 1;
        }

        /* Show when hovering over scrollbar */
        .custom-scrollbar-y:hover,
        .custom-scrollbar-x:hover {
            opacity: 1;
        }

        /* Always show when dragging */
        .dragging {
            opacity: 1 !important;
        }
    `;
    document.head.appendChild(style);

    // Create Y-axis scrollbar
    const scrollbarY = document.createElement('div');
    scrollbarY.className = 'custom-scrollbar-y';
    const thumbY = document.createElement('div');
    thumbY.className = 'custom-scrollbar-thumb';
    scrollbarY.appendChild(thumbY);
    document.body.appendChild(scrollbarY);

    // Create X-axis scrollbar
    const scrollbarX = document.createElement('div');
    scrollbarX.className = 'custom-scrollbar-x';
    const thumbX = document.createElement('div');
    thumbX.className = 'custom-scrollbar-thumb';
    scrollbarX.appendChild(thumbX);
    document.body.appendChild(scrollbarX);

    // Global variables
    let isDraggingY = false;
    let isDraggingX = false;
    let startY, startX, startScrollTop, startScrollLeft;
    let scrollTimeout;
    const html = document.documentElement;

    function getDocumentDimensions() {
        return {
            clientWidth: window.innerWidth || html.clientWidth || document.body.clientWidth,
            clientHeight: window.innerHeight || html.clientHeight || document.body.clientHeight,
            scrollWidth: Math.max(
                document.body.scrollWidth, html.scrollWidth,
                document.body.offsetWidth, html.offsetWidth,
                document.body.clientWidth, html.clientWidth
            ),
            scrollHeight: Math.max(
                document.body.scrollHeight, html.scrollHeight,
                document.body.offsetHeight, html.offsetHeight,
                document.body.clientHeight, html.clientHeight
            ),
            scrollLeft: window.pageXOffset || html.scrollLeft || document.body.scrollLeft,
            scrollTop: window.pageYOffset || html.scrollTop || document.body.scrollTop
        };
    }

    function updateScrollbars() {
        const dims = getDocumentDimensions();

        const maxScrollLeft = dims.scrollWidth - dims.clientWidth;
        const maxScrollTop = dims.scrollHeight - dims.clientHeight;

        const isScrollableY = dims.scrollHeight > dims.clientHeight;
        scrollbarY.style.display = isScrollableY ? 'block' : 'none';

        if (isScrollableY) {
            const thumbHeightRatio = dims.clientHeight / dims.scrollHeight;
            const thumbHeight = Math.max(thumbHeightRatio * dims.clientHeight, 20);
            const scrollRatioY = maxScrollTop > 0 ? dims.scrollTop / maxScrollTop : 0;
            const thumbTop = scrollRatioY * (dims.clientHeight - thumbHeight);

            thumbY.style.height = `${thumbHeight}px`;
            thumbY.style.top = `${thumbTop}px`;
        }

        const isScrollableX = dims.scrollWidth > dims.clientWidth;
        scrollbarX.style.display = isScrollableX ? 'block' : 'none';

        if (isScrollableX) {
            const thumbWidthRatio = dims.clientWidth / dims.scrollWidth;
            const thumbWidth = Math.max(thumbWidthRatio * dims.clientWidth, 20);
            const scrollRatioX = maxScrollLeft > 0 ? dims.scrollLeft / maxScrollLeft : 0;
            const thumbLeft = scrollRatioX * (dims.clientWidth - thumbWidth);

            thumbX.style.width = `${thumbWidth}px`;
            thumbX.style.left = `${thumbLeft}px`;
        }

        if (!isDraggingX && !isDraggingY) {
            html.classList.add('scrolling');
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {
                html.classList.remove('scrolling');
            }, 500);
        }
    }

    thumbY.addEventListener('mousedown', (e) => {
        e.preventDefault();
        e.stopPropagation();

        const dims = getDocumentDimensions();
        isDraggingY = true;
        startY = e.clientY;
        startScrollTop = dims.scrollTop;

        scrollbarY.classList.add('dragging');
        html.classList.add('scrolling');
        document.body.style.userSelect = 'none';
    });

    thumbX.addEventListener('mousedown', (e) => {
        e.preventDefault();
        e.stopPropagation();

        const dims = getDocumentDimensions();
        isDraggingX = true;
        startX = e.clientX;
        startScrollLeft = dims.scrollLeft;

        scrollbarX.classList.add('dragging');
        html.classList.add('scrolling');
        document.body.style.userSelect = 'none';
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDraggingY && !isDraggingX) return;

        const dims = getDocumentDimensions();

        if (isDraggingY) {
            const deltaY = e.clientY - startY;
            const thumbHeight = parseFloat(thumbY.style.height) || 20;
            const trackHeight = dims.clientHeight;
            const maxScrollTop = dims.scrollHeight - dims.clientHeight;

            const scrollRatio = maxScrollTop / (trackHeight - thumbHeight);
            const newScrollTop = Math.max(0, Math.min(maxScrollTop, startScrollTop + (deltaY * scrollRatio)));

            window.scrollTo(dims.scrollLeft, newScrollTop);
        }

        if (isDraggingX) {
            const deltaX = e.clientX - startX;
            const thumbWidth = parseFloat(thumbX.style.width) || 20;
            const trackWidth = dims.clientWidth;
            const maxScrollLeft = dims.scrollWidth - dims.clientWidth;

            const scrollRatio = maxScrollLeft / (trackWidth - thumbWidth);
            const newScrollLeft = Math.max(0, Math.min(maxScrollLeft, startScrollLeft + (deltaX * scrollRatio)));

            document.documentElement.scrollLeft = newScrollLeft;
            document.body.scrollLeft = newScrollLeft;
            window.scrollTo(newScrollLeft, dims.scrollTop);
        }

        updateScrollbars();
    });

    scrollbarY.addEventListener('mousedown', (e) => {
        if (e.target !== scrollbarY) return;

        const dims = getDocumentDimensions();
        const thumbHeight = parseFloat(thumbY.style.height) || 20;
        const clickPos = e.clientY;
        const thumbHalf = thumbHeight / 2;

        const trackHeight = dims.clientHeight;
        const maxScrollTop = dims.scrollHeight - dims.clientHeight;
        const clickRatio = (clickPos - thumbHalf) / (trackHeight - thumbHeight);
        const newScrollTop = Math.max(0, Math.min(maxScrollTop, clickRatio * maxScrollTop));

        window.scrollTo({
            top: newScrollTop,
            left: dims.scrollLeft,
            behavior: 'smooth'
        });
    });

    scrollbarX.addEventListener('mousedown', (e) => {
        if (e.target !== scrollbarX) return;

        const dims = getDocumentDimensions();
        const thumbWidth = parseFloat(thumbX.style.width) || 20;
        const clickPos = e.clientX;
        const thumbHalf = thumbWidth / 2;

        const trackWidth = dims.clientWidth;
        const maxScrollLeft = dims.scrollWidth - dims.clientWidth;
        const clickRatio = (clickPos - thumbHalf) / (trackWidth - thumbWidth);
        const newScrollLeft = Math.max(0, Math.min(maxScrollLeft, clickRatio * maxScrollLeft));

        window.scrollTo({
            top: dims.scrollTop,
            left: newScrollLeft,
            behavior: 'smooth'
        });
    });

    function endDragging() {
        if (isDraggingY) {
            isDraggingY = false;
            scrollbarY.classList.remove('dragging');
        }

        if (isDraggingX) {
            isDraggingX = false;
            scrollbarX.classList.remove('dragging');
        }

        document.body.style.userSelect = '';

        clearTimeout(scrollTimeout);
        scrollTimeout = setTimeout(() => {
            html.classList.remove('scrolling');
        }, 500);
    }

    document.addEventListener('mouseup', endDragging);
    document.addEventListener('mouseleave', endDragging);

    window.addEventListener('scroll', updateScrollbars, { passive: true });
    window.addEventListener('resize', updateScrollbars, { passive: true });

    function initialize() {
        updateScrollbars();
        setTimeout(updateScrollbars, 100);
        setTimeout(updateScrollbars, 500);
    }

    if (document.readyState === 'complete') {
        initialize();
    } else {
        window.addEventListener('load', initialize);
    }
})();