您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[en] A modern scroll progress bar at the bottom of the screen and smart scroll-to-top/bottom buttons with improved dark mode support (SVG, discreet, SPA & mobile friendly).
// ==UserScript== // @name Progress Bar and Quick Up and Down Buttons // @name:pt-BR BF - Barra de progressão e Botões de Subida e Decida Rápido // @namespace https://github.com/BrunoFortunatto // @version 1.1 // @description [en] A modern scroll progress bar at the bottom of the screen and smart scroll-to-top/bottom buttons with improved dark mode support (SVG, discreet, SPA & mobile friendly). // @description:pt-BR Adiciona uma barra de progresso de rolagem moderna na parte inferior da tela e botões de subir/descer inteligentes com suporte a modo escuro aprimorado (SVG, discretos, compatíveis com SPA e mobile). // @author Bruno Fortunato // @match *://*/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // VERIFICAÇÃO PARA EVITAR IFRAMES if (window.self !== window.top) { // Se este script estiver rodando dentro de um iframe, ele para aqui. return; } const INACTIVITY_TIMEOUT = 2000; // Tempo em milissegundos (2 segundos) para esconder os botões const RIGHT_EDGE_THRESHOLD_PX = 100; // Distância da borda direita para ativar os botões no PC let inactivityTimer; let buttonContainer; let progressBar; // --- Funções Auxiliares para Controle de Tema --- function applyTheme(isDarkMode) { // Cores para os botões const lightButtonBg = 'rgba(0, 123, 255, 0.5)'; // Azul claro padrão const darkButtonBg = 'rgba(50, 50, 70, 0.7)'; // Cinza escuro para o modo escuro const lightButtonHoverBg = 'rgba(0, 123, 255, 0.9)'; // Azul mais forte no hover const darkButtonHoverBg = 'rgba(80, 80, 100, 0.9)'; // Cinza mais forte no hover const buttonShadow = '0 3px 6px rgba(0,0,0,0.4)'; // Sombra padrão para ambos (ajusta a opacidade para ser visível) const darkButtonShadow = '0 3px 10px rgba(0,0,0,0.6)'; // Sombra mais intensa no modo escuro para contraste // Cores para a barra de progresso const lightProgressBarBg = 'linear-gradient(to right, #007bff, #00c7ff, #007bff)'; // Gradiente azul padrão // Gradiente para modo escuro: Mantenho tons escuros no preenchimento mas adiciono um brilho mais evidente const darkProgressBarBg = 'linear-gradient(to right, #3498db, #4a69bd, #3498db)'; // Azul mais perceptível no escuro const lightProgressBarShadow = '0 -2px 10px rgba(0, 123, 255, 0.7)'; // Sombra azul brilhante padrão // Nova sombra para modo escuro: mais intensa e com brilho para "luz" const darkProgressBarShadow = '0 -2px 12px rgba(173, 216, 230, 0.8), 0 -0.5px 5px rgba(255, 255, 255, 0.3)'; // Brilho azul claro/branco const textColor = 'white'; // Cor do texto/ícones permanece branco if (buttonContainer) { buttonContainer.querySelectorAll('button').forEach(button => { button.style.backgroundColor = isDarkMode ? darkButtonBg : lightButtonBg; button.style.boxShadow = isDarkMode ? darkButtonShadow : buttonShadow; // Aplica a sombra específica do tema button.onmouseover = () => Object.assign(button.style, { backgroundColor: isDarkMode ? darkButtonHoverBg : lightButtonHoverBg, transform: 'scale(1.05)' }); button.onmouseout = () => Object.assign(button.style, { backgroundColor: isDarkMode ? darkButtonBg : lightButtonBg, transform: 'scale(1)' }); button.style.color = textColor; }); } if (progressBar) { progressBar.style.background = isDarkMode ? darkProgressBarBg : lightProgressBarBg; progressBar.style.boxShadow = isDarkMode ? darkProgressBarShadow : lightProgressBarShadow; } } function detectAndApplyTheme() { const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; applyTheme(prefersDarkMode); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', detectAndApplyTheme); // --- Funções Auxiliares para Controle dos Botões --- function hideButtons() { if (buttonContainer) { buttonContainer.style.opacity = '0'; buttonContainer.style.pointerEvents = 'none'; } } function showButtonsAndResetTimer() { const scrolledEnough = document.body.scrollTop > 20 || document.documentElement.scrollTop > 20; const pageIsScrollable = document.body.scrollHeight > window.innerHeight; if (scrolledEnough && pageIsScrollable) { if (buttonContainer) { buttonContainer.style.opacity = '1'; buttonContainer.style.pointerEvents = 'auto'; clearTimeout(inactivityTimer); inactivityTimer = setTimeout(hideButtons, INACTIVITY_TIMEOUT); } } else { hideButtons(); clearTimeout(inactivityTimer); } } // --- Funções para a Barra de Progresso e Rolagem --- function getScrollableElement() { return document.documentElement.scrollTop > 0 || document.documentElement.scrollHeight > document.documentElement.clientHeight ? document.documentElement : document.body; } function updateProgressBar() { const scrollElem = getScrollableElement(); const scrollTop = scrollElem.scrollTop; const scrollHeight = scrollElem.scrollHeight; const clientHeight = scrollElem.clientHeight; const totalScrollableHeight = scrollHeight - clientHeight; let scrollProgress = 0; if (totalScrollableHeight > 0) { scrollProgress = (scrollTop / totalScrollableHeight) * 100; progressBar.style.width = scrollProgress + '%'; progressBar.style.display = 'block'; } else { progressBar.style.width = '0%'; progressBar.style.display = 'none'; } } // --- Inicialização dos Elementos (Botões e Barra de Progresso) --- function initializeScrollElements() { // --- Inicialização dos Botões --- if (buttonContainer && buttonContainer.parentNode) { buttonContainer.parentNode.removeChild(buttonContainer); } buttonContainer = document.createElement('div'); buttonContainer.style.position = 'fixed'; buttonContainer.style.right = '20px'; buttonContainer.style.top = '50%'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.zIndex = '9999'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.gap = '10px'; buttonContainer.style.opacity = '0'; buttonContainer.style.transition = 'opacity 0.3s ease-in-out'; buttonContainer.style.pointerEvents = 'none'; document.body.appendChild(buttonContainer); const baseButtonStyle = { color: 'white', border: 'none', borderRadius: '50%', width: '50px', height: '50px', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer', // box-shadow será definido por applyTheme transition: 'background-color 0.2s ease, transform 0.2s ease', }; const applyBaseStyle = (button) => Object.assign(button.style, baseButtonStyle); const topArrowSVG = ` <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="12 19 12 5"></polyline> <polyline points="5 12 12 5 19 12"></polyline> </svg> `; const bottomArrowSVG = ` <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="12 5 12 19"></polyline> <polyline points="5 12 12 19 19 12"></polyline> </svg> `; const topButton = document.createElement('button'); applyBaseStyle(topButton); topButton.innerHTML = topArrowSVG; topButton.onclick = () => { window.scrollTo({ top: 0, behavior: 'smooth' }); showButtonsAndResetTimer(); }; buttonContainer.appendChild(topButton); const bottomButton = document.createElement('button'); applyBaseStyle(bottomButton); bottomButton.innerHTML = bottomArrowSVG; bottomButton.onclick = () => { const scrollElem = getScrollableElement(); const totalHeight = scrollElem.scrollHeight - scrollElem.clientHeight; window.scrollTo({ top: totalHeight, behavior: 'smooth' }); showButtonsAndResetTimer(); }; buttonContainer.appendChild(bottomButton); // --- Inicialização da Barra de Progresso --- if (progressBar && progressBar.parentNode) { progressBar.parentNode.removeChild(progressBar); } progressBar = document.createElement('div'); progressBar.style.position = 'fixed'; progressBar.style.bottom = '0'; progressBar.style.left = '0'; progressBar.style.width = '0%'; progressBar.style.height = '5px'; progressBar.style.zIndex = '10000'; progressBar.style.transition = 'width 0.2s ease-out'; progressBar.style.display = 'none'; document.body.appendChild(progressBar); // --- Aplica o tema inicial --- detectAndApplyTheme(); // --- Eventos para mostrar/esconder os botões e atualizar a barra de progresso --- window.onscroll = () => { showButtonsAndResetTimer(); updateProgressBar(); }; document.onmousemove = (event) => { if (event.clientX > (window.innerWidth - RIGHT_EDGE_THRESHOLD_PX)) { showButtonsAndResetTimer(); } }; document.addEventListener('touchstart', showButtonsAndResetTimer, { passive: true }); document.addEventListener('touchmove', showButtonsAndResetTimer, { passive: true }); // --- Observador de Mutação para SPAs (detecta mudanças no DOM) --- const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList' || mutation.type === 'subtree') { showButtonsAndResetTimer(); updateProgressBar(); detectAndApplyTheme(); // Reaplicar tema em SPAs que mudam muito o DOM } }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); // --- Intercepta a API de Histórico para SPAs (detecta mudanças de URL sem reload) --- const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function() { originalPushState.apply(this, arguments); showButtonsAndResetTimer(); updateProgressBar(); detectAndApplyTheme(); // Reaplicar tema em SPAs }; history.replaceState = function() { originalReplaceState.apply(this, arguments); showButtonsAndResetTimer(); updateProgressBar(); detectAndApplyTheme(); // Reaplicar tema em SPAs }; // Garante que os elementos apareçam/desapareçam/atualizem corretamente na carga inicial window.addEventListener('load', () => { showButtonsAndResetTimer(); updateProgressBar(); detectAndApplyTheme(); }); window.addEventListener('DOMContentLoaded', () => { showButtonsAndResetTimer(); updateProgressBar(); detectAndApplyTheme(); }); } // Inicializa todos os elementos quando o script é carregado initializeScrollElements(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址