AutoScroller UI Plus ✨

自动滚动页面,rAF 丝滑驱动 + 可调速面板(兼容无限下拉)

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

  1. // ==UserScript==
  2. // @name AutoScroller UI Plus ✨
  3. // @namespace https://example.com/
  4. // @version 0.5
  5. // @description 自动滚动页面,rAF 丝滑驱动 + 可调速面板(兼容无限下拉)
  6. // @author 你
  7. // @license MIT
  8. // @match *://*/*
  9. // @icon https://example.com/icon.png
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. /* ——— 可自定义变量 ——— */
  17. let su_du = 200; // 滚动速度(px / 秒)← 可自定义
  18. let yun_xing = false; // 运行标志
  19. let shang_ci = 0; // 上次帧时间戳
  20.  
  21. /* ——— 创建 UI ——— */
  22. const kuang = document.createElement('div');
  23. kuang.id = 'autoScrollerPanel';
  24. kuang.innerHTML = `
  25. <header id="tiaoTi">
  26. 自动滚动
  27. <button id="guanBiBtn" title="关闭">✖</button>
  28. </header>
  29. <div class="neiRong">
  30. <label>速度(px/s):
  31. <input type="number" id="suDuInput" value="${su_du}" />
  32. </label>
  33. <div class="btns">
  34. <button id="kaiShiBtn">开始 🚀</button>
  35. <button id="tingZhiBtn">停止 🛑</button>
  36. </div>
  37. </div>
  38. `;
  39. document.body.appendChild(kuang);
  40.  
  41. /* ——— 样式 ——— */
  42. GM_addStyle(`
  43. :root{
  44. --as-main:#6366f1;
  45. --as-main-light:#8b97ff;
  46. --as-bg:rgba(255,255,255,0.85);
  47. }
  48. #autoScrollerPanel{
  49. position:fixed; bottom:20px; right:20px;
  50. width:190px; border-radius:12px; z-index:9999;
  51. box-shadow:0 4px 18px rgba(0,0,0,.15);
  52. backdrop-filter:blur(10px); background:var(--as-bg);
  53. font-size:14px; color:#111; user-select:none;
  54. transition:box-shadow .2s;
  55. }
  56. #autoScrollerPanel header{
  57. background:var(--as-main); color:#fff;
  58. padding:8px 12px; border-radius:12px 12px 0 0;
  59. cursor:move; font-weight:600; letter-spacing:.5px;
  60. display:flex; align-items:center; justify-content:space-between;
  61. }
  62. #autoScrollerPanel #guanBiBtn{
  63. background:transparent; border:none; color:#fff; font-size:16px;
  64. cursor:pointer; line-height:1; padding:0 4px;
  65. }
  66. #autoScrollerPanel #guanBiBtn:hover{opacity:0.8;}
  67. #autoScrollerPanel .neiRong{padding:12px 14px;}
  68. #autoScrollerPanel input{
  69. width:78px; margin-left:4px; padding:2px 4px;
  70. border:1px solid #ccc; border-radius:6px;
  71. }
  72. #autoScrollerPanel .btns{margin-top:10px; display:flex; gap:8px;}
  73. #autoScrollerPanel button:not(#guanBiBtn){
  74. flex:1; padding:6px 0; border:none; border-radius:8px;
  75. background:var(--as-main); color:#fff; font-weight:500;
  76. box-shadow:0 2px 6px rgba(0,0,0,.15);
  77. transition:transform .2s, background .2s, box-shadow .2s;
  78. cursor:pointer;
  79. }
  80. #autoScrollerPanel button:not(#guanBiBtn):hover{
  81. background:var(--as-main-light); transform:translateY(-2px);
  82. box-shadow:0 6px 12px rgba(0,0,0,.2);
  83. }
  84. `);
  85.  
  86. /* ——— 事件 ——— */
  87. const suDuInput = kuang.querySelector('#suDuInput');
  88. const kaiShiBtn = kuang.querySelector('#kaiShiBtn');
  89. const tingZhiBtn = kuang.querySelector('#tingZhiBtn');
  90. const guanBiBtn = kuang.querySelector('#guanBiBtn');
  91.  
  92. kaiShiBtn.addEventListener('click', () => {
  93. su_du = parseFloat(suDuInput.value) || su_du;
  94. if (yun_xing) return; // 已在跑
  95. yun_xing = true;
  96. shang_ci = 0;
  97. requestAnimationFrame(gunDong);
  98. });
  99.  
  100. tingZhiBtn.addEventListener('click', () => {
  101. yun_xing = false;
  102. });
  103.  
  104. // 关闭按钮:停止 + 隐藏面板
  105. guanBiBtn.addEventListener('click', () => {
  106. yun_xing = false;
  107. kuang.remove();
  108. });
  109.  
  110. /* ——— 核心滚动 (rAF) ——— */
  111. function gunDong(time){
  112. if (!yun_xing) return;
  113. if (!shang_ci) shang_ci = time;
  114. const cha = time - shang_ci; // Δt (ms)
  115. const bu_chang = su_du * cha / 1000; // Δs = 速度 * 时间
  116. const diBuCha = document.body.scrollHeight - (window.scrollY + window.innerHeight);
  117.  
  118. if (diBuCha > 0) window.scrollBy(0, bu_chang);
  119. // 到底部但可能懒加载 → 维持循环
  120. shang_ci = time;
  121. requestAnimationFrame(gunDong);
  122. }
  123.  
  124. /* ——— 面板拖拽 ——— */
  125. (function drag(el, handle){
  126. let startX, startY, startL, startT, z = 9999;
  127. handle.addEventListener('mousedown', e=>{
  128. startX = e.clientX; startY = e.clientY;
  129. const rect = el.getBoundingClientRect();
  130. startL = rect.left; startT = rect.top;
  131. el.style.transition = 'none';
  132. document.addEventListener('mousemove', move);
  133. document.addEventListener('mouseup', up, {once:true});
  134. e.preventDefault();
  135. });
  136. function move(e){
  137. el.style.left = startL + (e.clientX - startX) + 'px';
  138. el.style.top = startT + (e.clientY - startY) + 'px';
  139. el.style.right = 'auto'; el.style.bottom = 'auto';
  140. el.style.position = 'fixed'; el.style.zIndex = ++z;
  141. }
  142. function up(){ document.removeEventListener('mousemove', move); }
  143. })(kuang, kuang.querySelector('#tiaoTi'));
  144. })();