您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Click to pick an element, then auto-scroll by exact pixels every specified frames, with a visual progress bar for remaining frames.
// ==UserScript== // @name AutoScroll to Selected Element (Pixels Every Frames) with Progress Bar // @description Click to pick an element, then auto-scroll by exact pixels every specified frames, with a visual progress bar for remaining frames. // @match *://*/* // @version 0.0.1.20250614211616 // @namespace https://greasyfork.org/users/1435046 // ==/UserScript== (function () { let pixelsPerStep = 1; let framesPerStep = 1; let isScrollingActive = false; let previousFrameTimestamp = null; let selectedTargetElement = null; let targetScrollPositionY = null; let frameCountSinceLastScroll = 0; let averageFrameTimeMilliseconds = 16.67; let frameTimeSampleCount = 0; let frameTimeSumMilliseconds = 0; let previousScrollPositionY = 0; let pixelsScrolledLastFrame = 0; let initialRemainingFrames = 0; const controlPanel = document.createElement("div"); Object.assign(controlPanel.style, { position: "fixed", top: "10px", right: "10px", background: "#333", color: "#fff", padding: "10px", zIndex: 10000, fontFamily: "monospace", borderRadius: "5px", lineHeight: "1.5", width: "260px", boxSizing: "border-box" }); document.body.appendChild(controlPanel); const statusDisplay = document.createElement("div"); statusDisplay.textContent = "No target selected"; Object.assign(statusDisplay.style, { height: "20px", whiteSpace: "nowrap", overflow: "hidden" }); controlPanel.appendChild(statusDisplay); const scrollSpeedDisplay = document.createElement("div"); scrollSpeedDisplay.textContent = `Speed: ${pixelsPerStep} pixels every ${framesPerStep} frames`; Object.assign(scrollSpeedDisplay.style, { marginTop: "10px", height: "20px", whiteSpace: "nowrap", overflow: "hidden" }); controlPanel.appendChild(scrollSpeedDisplay); const pixelsInputLabel = document.createElement("label"); pixelsInputLabel.textContent = "Pixels per step:"; controlPanel.appendChild(pixelsInputLabel); const pixelsInput = document.createElement("input"); pixelsInput.type = "number"; pixelsInput.min = "1"; pixelsInput.value = pixelsPerStep; Object.assign(pixelsInput.style, { width: "100%", boxSizing: "border-box", height: "25px" }); controlPanel.appendChild(pixelsInput); const pixelsScrolledLabel = document.createElement("label"); pixelsScrolledLabel.textContent = "Actual px scrolled last frame:"; controlPanel.appendChild(pixelsScrolledLabel); const pixelsScrolledInput = document.createElement("input"); pixelsScrolledInput.type = "number"; pixelsScrolledInput.readOnly = true; pixelsScrolledInput.value = "0"; Object.assign(pixelsScrolledInput.style, { width: "100%", boxSizing: "border-box", height: "25px", backgroundColor: "#555" }); controlPanel.appendChild(pixelsScrolledInput); const framesInputLabel = document.createElement("label"); framesInputLabel.textContent = "Frames per step:"; controlPanel.appendChild(framesInputLabel); const framesInput = document.createElement("input"); framesInput.type = "number"; framesInput.min = "1"; framesInput.value = framesPerStep; Object.assign(framesInput.style, { width: "100%", boxSizing: "border-box", height: "25px" }); controlPanel.appendChild(framesInput); const framesRemainingLabel = document.createElement("label"); framesRemainingLabel.textContent = "Frames remaining:"; controlPanel.appendChild(framesRemainingLabel); const framesRemainingInput = document.createElement("input"); framesRemainingInput.type = "number"; framesRemainingInput.readOnly = true; framesRemainingInput.value = "0"; Object.assign(framesRemainingInput.style, { width: "100%", boxSizing: "border-box", height: "25px", backgroundColor: "#555" }); controlPanel.appendChild(framesRemainingInput); const progressBar = document.createElement("progress"); progressBar.value = 0; progressBar.max = 0; Object.assign(progressBar.style, { width: "100%", height: "10px", marginTop: "5px", boxSizing: "border-box" }); controlPanel.appendChild(progressBar); const buttonSelectTarget = document.createElement("button"); buttonSelectTarget.textContent = "Select Target"; Object.assign(buttonSelectTarget.style, { display: "block", marginTop: "10px", width: "100%", height: "30px" }); controlPanel.appendChild(buttonSelectTarget); const buttonStartScrolling = document.createElement("button"); buttonStartScrolling.textContent = "Start Scrolling"; Object.assign(buttonStartScrolling.style, { display: "block", marginTop: "10px", width: "100%", height: "30px" }); buttonStartScrolling.disabled = true; controlPanel.appendChild(buttonStartScrolling); const buttonStopScrolling = document.createElement("button"); buttonStopScrolling.textContent = "Stop Scrolling"; Object.assign(buttonStopScrolling.style, { display: "block", marginTop: "10px", width: "100%", height: "30px" }); buttonStopScrolling.disabled = true; controlPanel.appendChild(buttonStopScrolling); function updateScrollSpeed() { pixelsPerStep = Math.max(1, Number(pixelsInput.value)); framesPerStep = Math.max(1, Number(framesInput.value)); scrollSpeedDisplay.textContent = `Speed: ${pixelsPerStep} pixels every ${framesPerStep} frames`; updateTimeEstimateDisplay(); } pixelsInput.addEventListener("input", updateScrollSpeed); framesInput.addEventListener("input", updateScrollSpeed); function getFormattedTimeFromNow(seconds) { const now = new Date(); now.setSeconds(now.getSeconds() + seconds); return now.toTimeString().split(" ")[0]; } function computeRemainingFrames() { if (targetScrollPositionY === null || pixelsPerStep === 0) return 0; const remainingPixels = targetScrollPositionY - window.scrollY; return Math.max(0, remainingPixels / pixelsPerStep); } function computeRemainingTimeInSeconds() { const remainingFrames = computeRemainingFrames(); return (remainingFrames * averageFrameTimeMilliseconds) / 1000; } function updateTimeEstimateDisplay() { if (!selectedTargetElement) { statusDisplay.textContent = "No target selected"; framesRemainingInput.value = "0"; progressBar.value = 0; } else { const remainingFrames = computeRemainingFrames(); const secondsRemaining = computeRemainingTimeInSeconds(); framesRemainingInput.value = remainingFrames.toFixed(0); progressBar.value = remainingFrames; const eta = getFormattedTimeFromNow(secondsRemaining); const timeLabel = Math.ceil(secondsRemaining); statusDisplay.textContent = isScrollingActive ? `Time left: ${timeLabel} s (ETA: ${eta})` : `Ready: ${timeLabel} s (ETA: ${eta})`; } } function animationStep(timestamp) { if (!isScrollingActive) return; if (!previousFrameTimestamp) previousFrameTimestamp = timestamp; const delta = timestamp - previousFrameTimestamp; frameTimeSumMilliseconds += delta; frameTimeSampleCount++; averageFrameTimeMilliseconds = frameTimeSumMilliseconds / frameTimeSampleCount; const currentY = window.scrollY; pixelsScrolledLastFrame = currentY - previousScrollPositionY; previousScrollPositionY = currentY; pixelsScrolledInput.value = pixelsScrolledLastFrame.toFixed(2); frameCountSinceLastScroll++; if (frameCountSinceLastScroll >= framesPerStep) { window.scrollBy(0, pixelsPerStep); frameCountSinceLastScroll = 0; } previousFrameTimestamp = timestamp; updateTimeEstimateDisplay(); if (window.scrollY + 0.5 >= targetScrollPositionY) { isScrollingActive = false; const arrivalTime = new Date().toTimeString().split(" ")[0]; statusDisplay.textContent = `Target reached: <${selectedTargetElement.tagName.toLowerCase()}> at ${arrivalTime}`; buttonStartScrolling.disabled = false; buttonStopScrolling.disabled = true; framesRemainingInput.value = "0"; progressBar.value = 0; return; } requestAnimationFrame(animationStep); } function startSmoothScrolling() { if (!selectedTargetElement) return; isScrollingActive = true; previousFrameTimestamp = null; frameCountSinceLastScroll = 0; previousScrollPositionY = window.scrollY; pixelsScrolledInput.value = "0"; initialRemainingFrames = computeRemainingFrames(); progressBar.max = initialRemainingFrames; progressBar.value = initialRemainingFrames; buttonStartScrolling.disabled = true; buttonStopScrolling.disabled = false; updateTimeEstimateDisplay(); requestAnimationFrame(animationStep); } function stopSmoothScrolling() { isScrollingActive = false; previousFrameTimestamp = null; frameCountSinceLastScroll = 0; pixelsScrolledInput.value = "0"; framesRemainingInput.value = "0"; progressBar.value = 0; buttonStartScrolling.disabled = false; buttonStopScrolling.disabled = true; statusDisplay.textContent = "Scrolling stopped"; } function initiateTargetElementSelection() { statusDisplay.textContent = "Click on target element"; document.body.style.cursor = "crosshair"; function handleClick(event) { event.preventDefault(); event.stopPropagation(); selectedTargetElement = event.target; const rect = selectedTargetElement.getBoundingClientRect(); const absoluteTop = rect.top + window.pageYOffset; const bottomVisibleY = absoluteTop - window.innerHeight + selectedTargetElement.offsetHeight; targetScrollPositionY = Math.max(0, bottomVisibleY); document.body.style.cursor = ""; document.removeEventListener("click", handleClick, true); buttonSelectTarget.disabled = false; buttonStartScrolling.disabled = false; buttonStopScrolling.disabled = true; statusDisplay.textContent = `Target set: <${selectedTargetElement.tagName.toLowerCase()}> at Y=${targetScrollPositionY.toFixed(2)}`; framesRemainingInput.value = computeRemainingFrames().toFixed(0); progressBar.max = computeRemainingFrames(); progressBar.value = computeRemainingFrames(); } document.addEventListener("click", handleClick, true); buttonSelectTarget.disabled = true; } buttonSelectTarget.addEventListener("click", initiateTargetElementSelection); buttonStartScrolling.addEventListener("click", startSmoothScrolling); buttonStopScrolling.addEventListener("click", stopSmoothScrolling); document.addEventListener("keydown", event => { if (event.key === "Escape") stopSmoothScrolling(); }); })();