- // ==UserScript==
- // @name Overlay Custom Scrollbar
- // @namespace http://tampermonkey.net/
- // @version 2.1
- // @description Custom X and Y scrollbars with drag-to-seek, stay visible during drag, 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;
- }
-
- /* 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 {
- // Client dimensions (viewport)
- clientWidth: window.innerWidth || html.clientWidth || document.body.clientWidth,
- clientHeight: window.innerHeight || html.clientHeight || document.body.clientHeight,
-
- // Scroll dimensions (total document)
- 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
- ),
-
- // Current scroll position
- scrollLeft: window.pageXOffset || html.scrollLeft || document.body.scrollLeft,
- scrollTop: window.pageYOffset || html.scrollTop || document.body.scrollTop
- };
- }
-
- function updateScrollbars() {
- const dims = getDocumentDimensions();
-
- // Calculate maximum scroll positions
- const maxScrollLeft = dims.scrollWidth - dims.clientWidth;
- const maxScrollTop = dims.scrollHeight - dims.clientHeight;
-
- // Y-axis scrollbar updates
- const isScrollableY = dims.scrollHeight > dims.clientHeight;
- scrollbarY.style.display = isScrollableY ? 'block' : 'none';
-
- if (isScrollableY) {
- // Calculate Y thumb height and position
- 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`;
- }
-
- // X-axis scrollbar updates
- const isScrollableX = dims.scrollWidth > dims.clientWidth;
- scrollbarX.style.display = isScrollableX ? 'block' : 'none';
-
- if (isScrollableX) {
- // Calculate X thumb width and position
- 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`;
- }
-
- // Show scrollbars during scrolling
- if (!isDraggingX && !isDraggingY) {
- html.classList.add('scrolling');
- clearTimeout(scrollTimeout);
- scrollTimeout = setTimeout(() => {
- html.classList.remove('scrolling');
- }, 500);
- }
- }
-
- // Y-axis drag handling
- 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';
- });
-
- // X-axis drag handling
- 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';
- });
-
- // Mouse move handling
- document.addEventListener('mousemove', (e) => {
- if (!isDraggingY && !isDraggingX) return;
-
- const dims = getDocumentDimensions();
-
- if (isDraggingY) {
- // Y-axis drag logic
- const deltaY = e.clientY - startY;
- const thumbHeight = parseFloat(thumbY.style.height) || 20;
- const trackHeight = dims.clientHeight;
- const maxScrollTop = dims.scrollHeight - dims.clientHeight;
-
- // Calculate how much to scroll based on drag distance
- const scrollRatio = maxScrollTop / (trackHeight - thumbHeight);
- const newScrollTop = Math.max(0, Math.min(maxScrollTop, startScrollTop + (deltaY * scrollRatio)));
-
- // Apply scroll
- window.scrollTo(dims.scrollLeft, newScrollTop);
- }
-
- if (isDraggingX) {
- // X-axis drag logic
- const deltaX = e.clientX - startX;
- const thumbWidth = parseFloat(thumbX.style.width) || 20;
- const trackWidth = dims.clientWidth;
- const maxScrollLeft = dims.scrollWidth - dims.clientWidth;
-
- // Calculate how much to scroll based on drag distance
- const scrollRatio = maxScrollLeft / (trackWidth - thumbWidth);
- const newScrollLeft = Math.max(0, Math.min(maxScrollLeft, startScrollLeft + (deltaX * scrollRatio)));
-
- // IMPORTANT: Use a direct DOM method for horizontal scrolling
- document.documentElement.scrollLeft = newScrollLeft;
- document.body.scrollLeft = newScrollLeft;
- window.scrollTo(newScrollLeft, dims.scrollTop);
- }
-
- // Ensure scrollbar visual updates
- updateScrollbars();
- });
-
- // Click on track to jump
- 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;
-
- // Center the thumb on the click position
- 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;
-
- // Center the thumb on the click position
- 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'
- });
- });
-
- // End dragging
- 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);
-
- // Listen for scroll events
- window.addEventListener('scroll', updateScrollbars, { passive: true });
- window.addEventListener('resize', updateScrollbars, { passive: true });
-
- // Make sure the script runs after the page has fully loaded
- function initialize() {
- updateScrollbars();
-
- // Force multiple updates to ensure correct initial state
- setTimeout(updateScrollbars, 100);
- setTimeout(updateScrollbars, 500);
- }
-
- if (document.readyState === 'complete') {
- initialize();
- } else {
- window.addEventListener('load', initialize);
- }
- })();