jQuery / メソッド / .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を読むインターネットが必要で、インストールは不要です。位置変更、範囲超過、初期化とConsole例をWindows Edgeと3.7.1・4.0.0で実行しました。

文書は最小2000px高、ボックスは文書top 500pxです。100pxの6行で内容600px、内部表示高160pxなので最大移動は600 - 160 = 440pxです。境界線は内部高に含めません。直接比較のため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にしますが、短い箱を末尾扱いするかはUIの目的で決めます。画像・フォント・サイズ変更で内容高や最大値も変わります。

同じページの開発者ツール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

空の取得はundefinedです。正常位置0と区別し、存在するはずなら.length、実際のスクロール要素、DOM(Document Object Model、文書オブジェクトモデル)の準備を確認します。

移動しない・値が違うとき

overflow-y:autoだけでも内容に合わせ高くなる箱にはスクロールが不要です。高さ・最大高さと実scrollHeight/clientHeightを確認します。祖先がスクロールしているのに子を選ぶと子の値は0のままの場合があります。

ページはwindowを対象にして内部箱と区別します。数値設定自体はjQueryの時間付き効果を要求しません。外部の滑らかなスクロールCSSや効果があれば実行時点も確認し、途中値を最終と断定しません。

常に整数・非負とは一般化しません。MDNは小数、column-reverseなど逆配置の負数、Safariの境界外反応を説明しています。確認した0〜440は通常配置の例とEdgeでの結果です。他の構造では方向とブラウザー動作を先に確認します。