AutoScroller UI Plus ✨

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

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

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