AutoScroll to Selected Element (Pixels Every Frames) with Progress Bar

Click to pick an element, then auto-scroll by exact pixels every specified frames, with a visual progress bar for remaining frames.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
  });
})();