您需要先安装一个扩展,例如 篡改猴、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();
- })();