Progress Bar and Quick Up and Down Buttons

[en] A modern scroll progress bar at the bottom of the screen and smart scroll-to-top/bottom buttons (SVG, discreet, SPA & mobile friendly).

当前为 2025-06-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Progress Bar and Quick Up and Down Buttons
  3. // @name:pt-BR BF - Barra de progressão e Botões de Subida e Decida Rápido
  4. // @namespace https://github.com/BrunoFortunatto
  5. // @version 1.0
  6. // @description [en] A modern scroll progress bar at the bottom of the screen and smart scroll-to-top/bottom buttons (SVG, discreet, SPA & mobile friendly).
  7. // @description:pt-BR Adiciona uma barra de progresso de rolagem moderna na parte inferior da tela e botões de subir/descer inteligentes (SVG, discretos, compatíveis com SPA e mobile).
  8. // @author Bruno Fortunato
  9. // @match *://*/*
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // VERIFICAÇÃO PARA EVITAR IFRAMES
  18. if (window.self !== window.top) {
  19. // Se este script estiver rodando dentro de um iframe, ele para aqui.
  20. return;
  21. }
  22.  
  23. const INACTIVITY_TIMEOUT = 2000; // Tempo em milissegundos (2 segundos) para esconder os botões
  24. const RIGHT_EDGE_THRESHOLD_PX = 100; // Distância da borda direita para ativar os botões no PC
  25. let inactivityTimer;
  26. let buttonContainer;
  27. let progressBar; // Nova variável para a barra de progresso
  28.  
  29. // --- Funções Auxiliares para Controle dos Botões ---
  30.  
  31. // Função para esconder os botões
  32. function hideButtons() {
  33. if (buttonContainer) {
  34. buttonContainer.style.opacity = '0';
  35. buttonContainer.style.pointerEvents = 'none'; // Desabilita cliques quando invisível
  36. }
  37. }
  38.  
  39. // Função para mostrar os botões e resetar o timer
  40. function showButtonsAndResetTimer() {
  41. // Verifica se a página é rolavel o suficiente antes de mostrar
  42. const scrolledEnough = document.body.scrollTop > 20 || document.documentElement.scrollTop > 20;
  43. const pageIsScrollable = document.body.scrollHeight > window.innerHeight;
  44.  
  45. if (scrolledEnough && pageIsScrollable) {
  46. if (buttonContainer) {
  47. buttonContainer.style.opacity = '1';
  48. buttonContainer.style.pointerEvents = 'auto'; // Habilita cliques
  49. clearTimeout(inactivityTimer);
  50. inactivityTimer = setTimeout(hideButtons, INACTIVITY_TIMEOUT);
  51. }
  52. } else {
  53. // Se não for rolavel ou estiver no topo, garante que estejam escondidos
  54. hideButtons();
  55. clearTimeout(inactivityTimer);
  56. }
  57. }
  58.  
  59. // --- Funções para a Barra de Progresso ---
  60.  
  61. function updateProgressBar() {
  62. const docElem = document.documentElement;
  63. const body = document.body;
  64. const scrollTop = docElem.scrollTop || body.scrollTop; // Posição de rolagem atual
  65. const scrollHeight = docElem.scrollHeight || body.scrollHeight; // Altura total do conteúdo
  66. const clientHeight = docElem.clientHeight || window.innerHeight; // Altura visível da janela
  67.  
  68. const totalScrollableHeight = scrollHeight - clientHeight; // Altura total que pode ser rolada
  69. let scrollProgress = 0;
  70.  
  71. if (totalScrollableHeight > 0) { // Apenas se a página for rolavel
  72. scrollProgress = (scrollTop / totalScrollableHeight) * 100;
  73. progressBar.style.width = scrollProgress + '%';
  74. progressBar.style.display = 'block'; // Mostra a barra
  75. } else {
  76. progressBar.style.width = '0%'; // Reseta a largura para 0
  77. progressBar.style.display = 'none'; // Esconde se a página não for rolavel
  78. }
  79. }
  80.  
  81.  
  82. // --- Inicialização dos Elementos (Botões e Barra de Progresso) ---
  83.  
  84. function initializeScrollElements() {
  85. // --- Inicialização dos Botões ---
  86. // Se o container de botões já existe, remove para recriar (útil para SPAs)
  87. if (buttonContainer && buttonContainer.parentNode) {
  88. buttonContainer.parentNode.removeChild(buttonContainer);
  89. }
  90.  
  91. // Cria o container para os botões para centralizá-los
  92. buttonContainer = document.createElement('div');
  93. buttonContainer.style.position = 'fixed';
  94. buttonContainer.style.right = '20px'; // Distância da margem direita
  95. buttonContainer.style.top = '50%'; // Começa no meio vertical
  96. buttonContainer.style.transform = 'translateY(-50%)'; // Ajusta para centralizar exatamente
  97. buttonContainer.style.zIndex = '9999';
  98. buttonContainer.style.display = 'flex';
  99. buttonContainer.style.flexDirection = 'column'; // Organiza os botões em coluna
  100. buttonContainer.style.gap = '10px'; // Espaço entre os botões
  101. buttonContainer.style.opacity = '0'; // Começa invisível
  102. buttonContainer.style.transition = 'opacity 0.3s ease-in-out'; // Transição suave para aparecer/desaparecer
  103. buttonContainer.style.pointerEvents = 'none'; // Desabilita cliques quando invisível
  104.  
  105. document.body.appendChild(buttonContainer);
  106.  
  107. // Estilo base para os botões
  108. const baseButtonStyle = {
  109. backgroundColor: 'rgba(0, 123, 255, 0.5)', // Azul com 50% de opacidade
  110. color: 'white', // Cor da seta (herda para o SVG)
  111. border: 'none',
  112. borderRadius: '50%', // Torna o botão circular
  113. width: '50px', // Largura para formar o círculo
  114. height: '50px', // Altura para formar o círculo
  115. display: 'flex',
  116. justifyContent: 'center',
  117. alignItems: 'center',
  118. cursor: 'pointer',
  119. boxShadow: '0 3px 6px rgba(0,0,0,0.3)', // Sombra mais proeminente
  120. transition: 'background-color 0.2s ease, transform 0.2s ease', // Transição suave para hover e click
  121. };
  122.  
  123. // Estilo para hover
  124. const hoverStyle = {
  125. backgroundColor: 'rgba(0, 123, 255, 0.9)', // Mais opaco ao passar o mouse
  126. transform: 'scale(1.05)', // Aumenta levemente ao passar o mouse
  127. };
  128.  
  129. // --- SVGs das setas ---
  130. const topArrowSVG = `
  131. <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">
  132. <polyline points="12 19 12 5"></polyline>
  133. <polyline points="5 12 12 5 19 12"></polyline>
  134. </svg>
  135. `;
  136.  
  137. const bottomArrowSVG = `
  138. <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">
  139. <polyline points="12 5 12 19"></polyline>
  140. <polyline points="5 12 12 19 19 12"></polyline>
  141. </svg>
  142. `;
  143.  
  144. // Cria o botão "Subir"
  145. const topButton = document.createElement('button');
  146. Object.assign(topButton.style, baseButtonStyle);
  147. topButton.innerHTML = topArrowSVG; // Adiciona o SVG ao botão
  148.  
  149. topButton.onmouseover = () => Object.assign(topButton.style, hoverStyle);
  150. topButton.onmouseout = () => Object.assign(topButton.style, baseButtonStyle);
  151. topButton.onclick = () => {
  152. window.scrollTo({
  153. top: 0,
  154. behavior: 'smooth'
  155. });
  156. showButtonsAndResetTimer(); // Resetar o timer após o clique
  157. };
  158. buttonContainer.appendChild(topButton);
  159.  
  160. // Cria o botão "Descer"
  161. const bottomButton = document.createElement('button');
  162. Object.assign(bottomButton.style, baseButtonStyle);
  163. bottomButton.innerHTML = bottomArrowSVG; // Adiciona o SVG ao botão
  164.  
  165. bottomButton.onmouseover = () => Object.assign(bottomButton.style, hoverStyle);
  166. bottomButton.onmouseout = () => Object.assign(bottomButton.style, baseButtonStyle);
  167. bottomButton.onclick = () => {
  168. window.scrollTo({
  169. top: document.body.scrollHeight,
  170. behavior: 'smooth'
  171. });
  172. showButtonsAndResetTimer(); // Resetar o timer após o clique
  173. };
  174. buttonContainer.appendChild(bottomButton);
  175.  
  176.  
  177. // --- Inicialização da Barra de Progresso ---
  178. // Se a barra de progresso já existe, remove para recriar
  179. if (progressBar && progressBar.parentNode) {
  180. progressBar.parentNode.removeChild(progressBar);
  181. }
  182.  
  183. progressBar = document.createElement('div');
  184. progressBar.style.position = 'fixed';
  185. progressBar.style.bottom = '0';
  186. progressBar.style.left = '0';
  187. progressBar.style.width = '0%'; // Começa com 0% de largura
  188. progressBar.style.height = '5px'; // Altura da barra
  189. progressBar.style.zIndex = '10000'; // Garante que fique acima de outros elementos
  190. progressBar.style.background = 'linear-gradient(to right, #007bff, #00c7ff, #007bff)'; // Gradiente azul com "luzes"
  191. progressBar.style.boxShadow = '0 -2px 10px rgba(0, 123, 255, 0.7)'; // Sombra com efeito de luz
  192. progressBar.style.transition = 'width 0.2s ease-out'; // Transição suave para o progresso
  193. progressBar.style.display = 'none'; // Inicialmente oculta
  194. document.body.appendChild(progressBar);
  195.  
  196.  
  197. // --- Eventos para mostrar/esconder os botões e atualizar a barra de progresso ---
  198.  
  199. // Eventos de rolagem (funciona para desktop e mobile)
  200. window.onscroll = () => {
  201. showButtonsAndResetTimer(); // Lógica dos botões
  202. updateProgressBar(); // Lógica da barra de progresso
  203. };
  204.  
  205. // Eventos de mouse para desktop: Só ativa os botões se o mouse estiver perto da borda direita
  206. document.onmousemove = (event) => {
  207. if (event.clientX > (window.innerWidth - RIGHT_EDGE_THRESHOLD_PX)) {
  208. showButtonsAndResetTimer();
  209. }
  210. };
  211.  
  212. // Eventos de toque para mobile (usando addEventListener para 'passive')
  213. document.addEventListener('touchstart', showButtonsAndResetTimer, { passive: true });
  214. document.addEventListener('touchmove', showButtonsAndResetTimer, { passive: true });
  215.  
  216.  
  217. // --- Observador de Mutação para SPAs (detecta mudanças no DOM) ---
  218. const observer = new MutationObserver(mutations => {
  219. mutations.forEach(mutation => {
  220. if (mutation.type === 'childList' || mutation.type === 'subtree') {
  221. showButtonsAndResetTimer(); // Para botões
  222. updateProgressBar(); // Para barra de progresso
  223. }
  224. });
  225. });
  226.  
  227. observer.observe(document.body, {
  228. childList: true,
  229. subtree: true,
  230. attributes: false,
  231. characterData: false
  232. });
  233.  
  234. // --- Intercepta a API de Histórico para SPAs (detecta mudanças de URL sem reload) ---
  235. const originalPushState = history.pushState;
  236. const originalReplaceState = history.replaceState;
  237.  
  238. history.pushState = function() {
  239. originalPushState.apply(this, arguments);
  240. showButtonsAndResetTimer(); // Para botões
  241. updateProgressBar(); // Para barra de progresso
  242. };
  243.  
  244. history.replaceState = function() {
  245. originalReplaceState.apply(this, arguments);
  246. showButtonsAndResetTimer(); // Para botões
  247. updateProgressBar(); // Para barra de progresso
  248. };
  249.  
  250. // Garante que os elementos apareçam/desapareçam/atualizem corretamente na carga inicial
  251. window.addEventListener('load', () => {
  252. showButtonsAndResetTimer();
  253. updateProgressBar();
  254. });
  255. window.addEventListener('DOMContentLoaded', () => {
  256. showButtonsAndResetTimer();
  257. updateProgressBar();
  258. });
  259. }
  260.  
  261. // Inicializa todos os elementos quando o script é carregado
  262. initializeScrollElements();
  263.  
  264. })();