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.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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