jQuery / Method / .scrollTop() - Read or Set Vertical Scroll Position

jQuery’s .scrollTop() reads the current vertical scroll position of the first selected target; .scrollTop(120) sets it on every selected target. Select $(window) for the page or the relevant element for an inner scrolling container. This does not read the element’s position in the document.

Choose the scrolling target

The .scrollTop() API measures content hidden above a scrolling area in CSS pixels. In ordinary top-to-bottom flow, zero is the top and values increase downward. It is not the screen coordinate of a scrollbar thumb or the element’s CSS top.

Task Target and call Requirement
Read page scrolling $(window).scrollTop() Document taller than viewport
Scroll inside a box to 120px $("#box").scrollTop(120) Constrained height and overflowing content
Return to top target.scrollTop(0) Select the actual scrolling target

Native readings are available as Element.scrollTop and window.scrollY. Here jQuery uses one method for both targets. Reading alone does not move the view.

Complete comparison of page and box scrolling

Save all code as UTF-8 in scroll-demo.html and open it in a browser. Internet is needed for the official jQuery file 4.0.0 from its content delivery network (CDN); no installation is required. Position changes, oversized requests, reset and the Console example were executed with 3.7.1 and 4.0.0 in Edge on Windows.

The document is at least 2000px tall, and the box is at document top 500px. Six rows of 100px give 600px content; its visible inner height is 160px, so maximum scrolling is 600 - 160 = 440px. Borders are outside that inner height. CSS scroll-behavior is auto for a direct comparison.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Separate page and element scroll positions</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    html { scroll-behavior: auto; }
    body { margin: 0; min-height: 2000px; font: 16px/1.6 sans-serif; }
    #controls { position: fixed; top: 0; left: 0; right: 0; z-index: 2; background: #fff; padding: 12px; max-height: 42vh; overflow: auto; }
    button { font: inherit; margin: 3px; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
    #box { position: absolute; top: 500px; left: 20px; right: 20px; max-width: 600px; height: 160px; overflow-y: auto; border: 2px solid teal; scroll-behavior: auto; }
    .row { height: 100px; box-sizing: border-box; padding: 12px; border-bottom: 1px solid #abc; background: #edf9f6; }
  </style>
</head>
<body>
  <div id="controls">
    <strong>Page and box scrolling</strong>
    <button type="button" id="box120">Box to 120</button>
    <button type="button" id="bottom">Box to bottom</button>
    <button type="button" id="page300">Page to 300</button>
    <button type="button" id="reset">Reset both</button>
    <pre id="result" aria-live="polite"></pre>
  </div>
  <div id="box" tabindex="0" aria-label="Scrollable example rows">
    <div class="row">Alpha</div><div class="row">Beta</div><div class="row">Gamma</div><div class="row">Delta</div><div class="row">Epsilon</div><div class="row">Zeta</div>
  </div>
  <script>
    $(function () {
      const $box = $("#box");
      function report() {
        const node = $box.get(0);
        const top = $box.scrollTop();
        const max = Math.max(0, node.scrollHeight - node.clientHeight);
        const bottom = max > 0 && Math.abs(max - top) <= 1;
        $("#result").text(`window=${$(window).scrollTop()}\nbox=${top}\nbox max=${max}\nat bottom=${bottom}`);
      }
      $("#box120").on("click", function () { $box.scrollTop(120); report(); });
      $("#bottom").on("click", function () { $box.scrollTop(1000000); report(); });
      $("#page300").on("click", function () { $(window).scrollTop(300); report(); });
      $("#reset").on("click", function () { $box.scrollTop(0); $(window).scrollTop(0); report(); });
      $box.on("scroll.scrollLesson", report);
      $(window).on("scroll.scrollLesson resize.scrollLesson", report);
      report();
    });
  </script>
</body>
</html>

This output assumes both positions start at the top. If your viewport is taller than 2000px and the page does not overflow, reduce its height to test page scrolling.

window=0
box=0
box max=440
at bottom=false
  1. Box to 120 gives box=120 while window stays zero. Part of Alpha disappears above and later rows become visible.
  2. Page to 300 gives window=300, box=120. Moving the box on screen differs from moving its contents inside.
  3. Box to bottom requests 1000000, but this normal box clamps at 440. at bottom becomes true.
  4. Scroll or use the keyboard inside the box to update its scroll report. Fixed controls keep both readings available.
  5. Reset both sets the box and window separately to zero. Repeated resets restore the initial output.

Register scrolling directly on the element that scrolls. Do not assume window scroll handles an inner box. report() reads both positions and recalculates the maximum each time.

Maximum, bottom detection and multiple elements

For an ordinary vertical container, maximum scrolling is Math.max(0, scrollHeight - clientHeight). scrollHeight measures content height; clientHeight measures the visible interior. The example requires a positive maximum and a difference of at most 1px to report bottom, allowing fractional positioning rather than exact numeric equality.

Without overflow, a request may leave position at zero. This example reports at bottom=false then; whether short containers should count as already at the bottom depends on your interface. Images, fonts or size changes can alter content height and the maximum.

Run this in the same page’s developer tools Console. It creates two temporary boxes at different positions. Reading the collection gives only the first; setting 80 affects both. Cleanup removes the temporary area.

const $holder = $("<div>").appendTo("body");
const $boxes = $("<div></div><div></div>").css({ height: 40, overflowY: "auto" }).appendTo($holder);
$boxes.each(function () { $(this).append($("<div>").css("height", 200)); });
$boxes.eq(0).scrollTop(40);
$boxes.eq(1).scrollTop(100);
console.log(`first getter=${$boxes.scrollTop()}`);
$boxes.scrollTop(80);
console.log(`all=${$boxes.map(function () { return $(this).scrollTop(); }).get().join(",")}`);
console.log(`empty getter=${typeof $().scrollTop()}`);
$holder.remove();
first getter=40
all=80,80
empty getter=undefined

An empty getter returns undefined. Zero is a valid position and differs from no target. If a target should exist, check .length, the actual scrolling element and Document Object Model (DOM) readiness.

Troubleshoot no movement or unexpected values

overflow-y:auto alone does not create scrolling if the box grows to fit all content. Check height or max-height with actual scrollHeight/clientHeight. If an ancestor scrolls but you select a child, that child’s position may remain zero.

Use window to distinguish page scrolling from containers. A numeric .scrollTop() setter does not itself request a timed jQuery animation. External smooth-scroll CSS or effects can affect timing; check the view and do not treat an intermediate reading as final.

Do not generalize that every value is an integer or nonnegative. MDN describes fractional values, negative positions in reversed layouts such as column-reverse and Safari overscroll beyond boundaries. The verified 0–440 range applies to this normal-flow box in Edge. Check direction and browser behavior for other layouts.