✨ Simple Pull to Refresh

🌟 Minimal Premium: Простое и стабильное обновление страницы потягиванием для браузера Via

  1. // ==UserScript==
  2. // @name ✨ Simple Pull to Refresh
  3. // @namespace r1kov
  4. // @version 0.3-fix
  5. // @description 🌟 Minimal Premium: Простое и стабильное обновление страницы потягиванием для браузера Via
  6. // @match *://*/*
  7. // @grant none
  8. // @license MIT
  9. // ==/UserScript==
  10. (function() {
  11. 'use strict';
  12.  
  13. const config = {
  14. threshold: 100, // Минимальное расстояние для срабатывания обновления
  15. elementId: 'simple-refresh',
  16. overlayId: 'simple-overlay',
  17. colors: {
  18. pull: '#333',
  19. release: '#ffcc00',
  20. loading: '#007aff'
  21. },
  22. messages: {
  23. pull: 'потяните вниз',
  24. release: 'отпустите',
  25. loading: 'обновление'
  26. }
  27. };
  28.  
  29. let state = {
  30. startY: 0,
  31. currentY: 0,
  32. isActive: false
  33. };
  34.  
  35. function injectStyles() {
  36. const style = document.createElement('style');
  37. style.textContent = `
  38. #${config.elementId} {
  39. position: fixed;
  40. top: 0;
  41. left: 0;
  42. right: 0;
  43. height: 64px;
  44. display: flex;
  45. align-items: center;
  46. justify-content: center;
  47. z-index: 999999;
  48. transform: translateY(-100%);
  49. transition: transform 0.3s ease-out;
  50. pointer-events: none;
  51. font-family: -apple-system, system-ui, sans-serif;
  52. }
  53. #${config.elementId} .pill {
  54. display: flex;
  55. align-items: center;
  56. gap: 8px;
  57. padding: 12px 20px;
  58. background: ${config.colors.pull};
  59. color: white;
  60. border-radius: 26px;
  61. font-size: 14px;
  62. font-weight: 500;
  63. transition: background 0.3s ease-out;
  64. }
  65. #${config.elementId} .icon {
  66. font-size: 18px;
  67. }
  68. #${config.overlayId} {
  69. position: fixed;
  70. top: 0;
  71. left: 0;
  72. right: 0;
  73. bottom: 0;
  74. background: rgba(255, 255, 255, 0);
  75. backdrop-filter: blur(0px);
  76. -webkit-backdrop-filter: blur(0px);
  77. z-index: 999998;
  78. pointer-events: none;
  79. transition: all 0.3s ease-out;
  80. }
  81. @keyframes rotate {
  82. to { transform: rotate(360deg); }
  83. }
  84. `;
  85. document.head.appendChild(style);
  86. }
  87.  
  88. function createElements() {
  89. if (document.getElementById(config.elementId) || document.getElementById(config.overlayId)) {
  90. return {
  91. overlay: document.getElementById(config.overlayId),
  92. refreshEl: document.getElementById(config.elementId)
  93. };
  94. }
  95.  
  96. const overlay = document.createElement('div');
  97. overlay.id = config.overlayId;
  98. document.body.appendChild(overlay);
  99.  
  100. const refreshEl = document.createElement('div');
  101. refreshEl.id = config.elementId;
  102. refreshEl.innerHTML = `
  103. <div class="pill">
  104. <span class="icon">↓</span>
  105. <span class="text">${config.messages.pull}</span>
  106. </div>
  107. `;
  108. document.body.appendChild(refreshEl);
  109.  
  110. return { overlay, refreshEl };
  111. }
  112.  
  113. function updateUI(distance) {
  114. const { overlay, refreshEl } = createElements();
  115. const translateY = Math.min(distance * 0.5, 64);
  116. refreshEl.style.transform = `translateY(${translateY - 64}px)`;
  117. overlay.style.background = `rgba(255, 255, 255, ${distance / config.threshold * 0.1})`;
  118. overlay.style.backdropFilter = `blur(${distance / config.threshold * 8}px)`;
  119. overlay.style.webkitBackdropFilter = `blur(${distance / config.threshold * 8}px)`;
  120.  
  121. if (distance > config.threshold) {
  122. refreshEl.querySelector('.pill').style.background = config.colors.release;
  123. refreshEl.querySelector('.text').textContent = config.messages.release;
  124. } else {
  125. refreshEl.querySelector('.pill').style.background = config.colors.pull;
  126. refreshEl.querySelector('.text').textContent = config.messages.pull;
  127. }
  128. }
  129.  
  130. function reset() {
  131. const { overlay, refreshEl } = createElements();
  132. refreshEl.style.transform = 'translateY(-100%)';
  133. overlay.style.background = 'rgba(255, 255, 255, 0)';
  134. overlay.style.backdropFilter = 'blur(0px)';
  135. overlay.style.webkitBackdropFilter = 'blur(0px)';
  136.  
  137. setTimeout(() => {
  138. if (refreshEl && refreshEl.parentNode) refreshEl.remove();
  139. if (overlay && overlay.parentNode) overlay.remove();
  140. }, 300);
  141. }
  142.  
  143. function refresh() {
  144. const { refreshEl } = createElements();
  145. refreshEl.querySelector('.pill').style.background = config.colors.loading;
  146. refreshEl.querySelector('.icon').textContent = '↻';
  147. refreshEl.querySelector('.text').textContent = config.messages.loading;
  148.  
  149. setTimeout(() => {
  150. window.location.reload();
  151. }, 300);
  152. }
  153.  
  154. function handleTouchStart(e) {
  155. if (window.scrollY === 0 && !e.target.matches('input, textarea, select')) {
  156. state.startY = e.touches[0].pageY;
  157. state.currentY = state.startY;
  158. state.isActive = true;
  159. }
  160. }
  161.  
  162. function handleTouchMove(e) {
  163. if (!state.isActive) return;
  164. state.currentY = e.touches[0].pageY;
  165. const distance = state.currentY - state.startY;
  166.  
  167. if (distance > 0 && window.scrollY === 0) {
  168. e.preventDefault();
  169. requestAnimationFrame(() => updateUI(distance));
  170. }
  171. }
  172.  
  173. function handleTouchEnd() {
  174. if (!state.isActive) return;
  175. const distance = state.currentY - state.startY;
  176.  
  177. if (distance > config.threshold) {
  178. refresh();
  179. } else {
  180. reset();
  181. }
  182.  
  183. state.isActive = false;
  184. }
  185.  
  186. injectStyles();
  187.  
  188. document.addEventListener('touchstart', handleTouchStart, { passive: true });
  189. document.addEventListener('touchmove', handleTouchMove, { passive: false });
  190. document.addEventListener('touchend', handleTouchEnd, { passive: true });
  191.  
  192. // Очистка элементов при переходе на другую страницу
  193. window.addEventListener('beforeunload', () => {
  194. const refreshEl = document.getElementById(config.elementId);
  195. const overlay = document.getElementById(config.overlayId);
  196.  
  197. if (refreshEl && refreshEl.parentNode) refreshEl.remove();
  198. if (overlay && overlay.parentNode) overlay.remove();
  199. });
  200. })();