jQuery / Method / .scrollTop() - 선택한 요소의 스크롤바 수직 위치를 반환하거나 스크롤바 수직 위치를 정하는 메서드

jQuery의 .scrollTop()은 선택한 첫 대상의 현재 세로 스크롤 위치를 숫자로 읽고, .scrollTop(120)은 선택한 모든 대상의 위치를 설정합니다. 페이지 전체는 $(window), 내부 스크롤 상자는 해당 요소를 선택합니다. 요소가 문서에서 놓인 좌표를 읽는 메서드와는 다릅니다.

스크롤 위치와 대상 구분하기

.scrollTop() 공식 문서의 값은 스크롤 영역 위쪽으로 가려진 내용의 양을 CSS 픽셀로 나타냅니다. 일반적인 위에서 아래로 흐르는 상자는 맨 위가 0이고 아래로 이동하면 값이 커집니다. 스크롤바 손잡이가 화면에서 놓인 좌표나 요소의 CSS top을 의미하지 않습니다.

원하는 작업 대상·호출 확인할 조건
페이지 전체 위치 읽기 $(window).scrollTop() 문서에 화면보다 긴 내용이 있음
상자 내부를 120px로 이동 $("#box").scrollTop(120) 높이 제한과 넘치는 내용이 있음
맨 위로 이동 대상.scrollTop(0) 실제로 스크롤되는 대상을 선택

네이티브 요소 값은 Element.scrollTop, 페이지 값은 window.scrollY로도 읽습니다. 이 예제에서는 jQuery가 두 대상을 같은 메서드로 다루는 모습을 비교합니다. 스크롤 위치 읽기만으로 화면은 움직이지 않습니다.

페이지와 내부 상자를 비교하는 전체 예제

전체 코드를 UTF-8로 scroll-demo.html에 저장하고 브라우저로 엽니다. 콘텐츠 전송망(CDN)의 공식 jQuery 파일 4.0.0을 읽을 인터넷 연결이 필요하며 설치는 없습니다. 아래 위치 변경, 범위 초과, 초기화와 콘솔 코드를 3.7.1·4.0.0 및 Windows Edge에서 실행했습니다.

문서는 최소 2000px 높이이고, 상자는 문서 top 500px에 놓였습니다. 상자 안의 여섯 행은 각각 100px, 보이는 내부 높이는 160px이므로 최대 스크롤은 600 - 160 = 440px입니다. 테두리는 이 내부 높이에 포함하지 않습니다. 실제 비교를 위해 CSS의 scroll-behavior를 auto로 두었습니다.

<!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>

처음 페이지와 상자 모두 맨 위일 때 출력입니다. 창이 2000px보다 높아 페이지가 넘치지 않는 환경에서는 페이지 이동을 확인하려면 창 높이를 줄입니다.

window=0
box=0
box max=440
at bottom=false
  1. Box to 120은 box=120으로 이동하고 window=0은 유지합니다. Alpha 일부가 위로 가려지고 다음 행이 보입니다.
  2. Page to 300은 window=300, box=120입니다. 문서 안의 상자가 화면에서 움직이는 것과 상자 안 내용의 스크롤은 별개입니다.
  3. Box to bottom은 1000000을 요청하지만 이 일반 상자의 실제 값은 최대 440에 제한됩니다. at bottom=true를 확인합니다.
  4. 상자 안에서 직접 스크롤하거나 키보드로 조작하면 해당 scroll 이벤트가 출력을 갱신합니다. 조작부는 고정해 두 위치를 계속 볼 수 있게 했습니다.
  5. Reset both는 상자와 페이지를 각각 0으로 되돌립니다. 여러 번 눌러도 초기 출력입니다.

실제로 스크롤한 요소의 이벤트를 직접 등록합니다. 상자 내부의 변경을 window의 scroll만으로 처리한다고 가정하지 않습니다. 코드의 report()는 두 위치를 읽고, 상자 최대값은 호출할 때마다 다시 계산합니다.

최대 위치·맨 아래와 여러 요소

일반적인 세로 스크롤 상자의 최대 이동량은 Math.max(0, scrollHeight - clientHeight)입니다. scrollHeight는 내용 높이, clientHeight는 내부 표시 높이입니다. 예제는 최대값이 양수이고 현재값과의 차이가 1px 이내면 맨 아래로 판정합니다. 숫자 하나의 엄격한 일치에 의존하지 않아 소수 위치의 오차를 허용합니다.

내용이 넘치지 않는 상자는 이동 요청을 해도 0일 수 있습니다. 예제는 이 경우 at bottom을 false로 두며, 모든 짧은 상자를 ‘이미 맨 아래’로 볼지 여부는 인터페이스 목적에 맞게 정합니다. 사진·폰트 로딩이나 크기 변경 뒤에는 내용 높이와 최대값도 달라질 수 있습니다.

같은 페이지의 개발자 도구 Console에서 다음 코드를 실행합니다. 임시 상자 두 개에 서로 다른 위치를 설정한 뒤 읽으면 첫 상자의 값만 나옵니다. 다시 전체에 80을 설정하면 두 상자가 모두 바뀌고, 끝에는 임시 영역을 제거합니다.

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

빈 선택의 getter는 undefined입니다. 위치 0은 정상적인 숫자이므로 대상 없음과 구분합니다. 대상이 있을 것으로 예상했다면 .length를 확인한 뒤 실제 스크롤 요소와 DOM(Document Object Model, 문서 객체 모델) 준비 시점을 확인합니다.

스크롤되지 않거나 값이 다를 때

상자에 overflow-y:auto만 있어도 높이가 내용만큼 늘어나면 내부 스크롤이 필요하지 않습니다. 높이 또는 최대 높이, 실제 scrollHeight·clientHeight를 함께 확인합니다. 반대로 스크롤은 바깥 조상에서 일어나는데 자식을 선택하면 자식의 값은 계속 0일 수 있습니다.

페이지 전체는 window를 기준으로 처리하면 내부 컨테이너와 혼동하기 쉽지 않습니다. .scrollTop()의 숫자 설정은 그 자체로 jQuery 시간 지정 애니메이션을 요청하지 않습니다. 외부 CSS의 부드러운 스크롤 설정이나 별도의 효과 코드가 있다면 실행 시점과 화면 반응을 함께 확인하고, 아직 이동 중인 값을 최종 결과로 단정하지 않습니다.

모든 브라우저·배치에서 값이 항상 정수이고 0 이상이라고 일반화하지 않습니다. MDN은 소수 값, column-reverse 같은 역방향 배치의 음수 값, Safari의 경계 바깥 스크롤 반응을 설명합니다. 여기서 확인한 0~440 제한은 일반 흐름의 예제 상자와 Edge 환경에 대한 결과입니다. 다른 배치에서는 해당 구조의 시작·끝 방향과 브라우저 동작을 먼저 확인합니다.