【jQuery】最後までスクロールしたらボタンを活性化する実装完全ガイド|利用規約・同意ボタン対応

「利用規約を最後まで読んだら同意ボタンを押せるようにする」という実装は、Webアプリやサービス登録フォームでよく使われます。jQueryを使えばスクロール位置の検出・ボタンの活性/非活性・プログレスバー連動まで簡潔に実装できます。

この記事でわかること

  • スクロールが一番下まで達したか判定する方法
  • 最後までスクロールしたらボタンを活性化する実装
  • スクロール位置をプログレスバーで可視化する方法
  • ウィンドウ全体とコンテナ内スクロールの違い
  • スクロール判定のしきい値調整・UXのコツ
スポンサーリンク

1. スクロール位置の判定ロジック

スクロールが一番下に達したかどうかは「現在のスクロール位置 + ウィンドウ高さ ≥ ドキュメント全体の高さ」で判定します。

// スクロール位置の計算
$(window).on('scroll', function () {
  var scrollTop    = $(window).scrollTop();    // 現在のスクロール量(上からのpx)
  var windowHeight = $(window).height();       // ウィンドウの表示高さ
  var docHeight    = $(document).height();     // ページ全体の高さ

  // 一番下まで達したか判定
  var isBottom = scrollTop + windowHeight >= docHeight;

  console.log('スクロール率:', Math.round((scrollTop + windowHeight) / docHeight * 100) + '%');
  console.log('最下部到達:', isBottom);
});
ページ高さは $(document).height() で取得
$(window).height() はブラウザの表示領域(ビューポート)の高さ、$(document).height() はページ全体のコンテンツ高さです。この2つの違いを理解することが正確な判定のポイントです。

2. 最後までスクロールしたらボタンを活性化する

利用規約画面でよく使われるパターンです。スクロールが完了するまで同意ボタンをdisabledにしておきます。

<div id="terms-box">
  <p>利用規約の内容...(長い文章)</p>
</div>
<button id="agree-btn" disabled>同意して続ける</button>
#terms-box {
  height: 300px;
  overflow-y: scroll;
  border: 1px solid #ccc;
  padding: 16px;
}

#agree-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
#agree-btn:not(:disabled) {
  background-color: #0284c7;
  color: #fff;
  cursor: pointer;
}
$(function () {
  var $box = $('#terms-box');
  var $btn = $('#agree-btn');

  $box.on('scroll', function () {
    var scrollTop    = $box.scrollTop();
    var innerHeight  = $box.innerHeight();
    var scrollHeight = $box[0].scrollHeight;  // コンテンツ全体の高さ

    // 最下部まで到達したか(2px のマージンを設ける)
    var isBottom = scrollTop + innerHeight >= scrollHeight - 2;

    if (isBottom) {
      $btn.prop('disabled', false);
    }
  });
});
ウィンドウスクロールとコンテナスクロールは別
上記のコードは #terms-box という要素内のスクロールを監視しています。ページ全体のスクロールを監視する場合は $(window).on('scroll')$(document).height() を使います(セクション1参照)。

3. スクロール率をプログレスバーで可視化

「あとどのくらい読めばよいか」をユーザーに伝えるプログレスバーと組み合わせると UX が向上します。

<div id="progress-bar" style="height:4px; background:#e2e8f0;">
  <div id="progress-fill" style="height:100%; width:0%; background:#0284c7; transition:width 0.1s;"></div>
</div>
<div id="terms-box2">
  <p>利用規約の内容...</p>
</div>
<button id="agree-btn2" disabled>同意して続ける</button>
$(function () {
  var $box  = $('#terms-box2');
  var $fill = $('#progress-fill');
  var $btn  = $('#agree-btn2');

  $box.on('scroll', function () {
    var scrollTop    = $box.scrollTop();
    var innerHeight  = $box.innerHeight();
    var scrollHeight = $box[0].scrollHeight;

    // スクロール率(0〜100)を計算
    var maxScroll  = scrollHeight - innerHeight;
    var pct        = maxScroll > 0 ? Math.min(100, Math.round(scrollTop / maxScroll * 100)) : 100;

    $fill.css('width', pct + '%');

    if (pct >= 100) {
      $btn.prop('disabled', false);
    }
  });
});

4. ページ全体のスクロールでボタンを活性化

コンテナではなくページ全体を読み終えたら送信ボタンを有効にするパターンです。

$(function () {
  var $btn      = $('#submit-btn');
  var threshold = 100;  // 下端から何px以内で「読み終えた」とみなすか

  $(window).on('scroll', function () {
    var scrollTop    = $(window).scrollTop();
    var windowHeight = $(window).height();
    var docHeight    = $(document).height();

    if (scrollTop + windowHeight >= docHeight - threshold) {
      $btn.prop('disabled', false)
          .text('送信する');
    }
  });
});
threshold(しきい値)のUXへの影響
厳密に「最下部ピッタリ」(threshold=0)にすると、ブラウザの小数点スクロール誤差で永遠に到達しないケースがあります。threshold = 2〜10 程度のマージンを設けるのがベストプラクティスです。

5. 一度活性化したら無効に戻さない

スクロールイベントは頻繁に発火するため、一度ボタンを活性化したらイベントの監視を止めてパフォーマンスを改善します。

$(function () {
  var $box = $('#terms-box');
  var $btn = $('#agree-btn');

  function onScroll() {
    var scrollTop    = $box.scrollTop();
    var innerHeight  = $box.innerHeight();
    var scrollHeight = $box[0].scrollHeight;

    if (scrollTop + innerHeight >= scrollHeight - 2) {
      $btn.prop('disabled', false);
      $box.off('scroll', onScroll);  // イベント解除で無駄な処理を止める
    }
  }

  $box.on('scroll', onScroll);
});

6. コンテンツが短くて最初からスクロール不要な場合の対策

利用規約が短くてスクロールが発生しない場合、ユーザーはボタンを押せません。コンテンツがスクロール不要なら初期状態から活性化する配慮が必要です。

$(function () {
  var $box = $('#terms-box');
  var $btn = $('#agree-btn');

  function checkScrollable() {
    // コンテンツがボックスより短い場合はスクロール不要
    if ($box[0].scrollHeight <= $box.innerHeight()) {
      $btn.prop('disabled', false);
      return;
    }
    $box.on('scroll', onScroll);
  }

  function onScroll() {
    var scrollTop    = $box.scrollTop();
    var innerHeight  = $box.innerHeight();
    var scrollHeight = $box[0].scrollHeight;
    if (scrollTop + innerHeight >= scrollHeight - 2) {
      $btn.prop('disabled', false);
      $box.off('scroll', onScroll);
    }
  }

  checkScrollable();
});

まとめ

最後までスクロールしたらボタンを活性化する実装のポイントをまとめます。

ポイント 内容
コンテナ内スクロール判定 scrollTop + innerHeight >= scrollHeight - 2
ページ全体のスクロール判定 scrollTop + $(window).height() >= $(document).height()
しきい値(threshold) 2〜10px のマージンで確実に到達判定
パフォーマンス対策 活性化後に .off('scroll') でイベント解除
コンテンツが短い場合 初期チェックで scrollHeight <= innerHeight なら即時活性化

関連記事: jQueryでスムーズスクロール機能付きのトップに戻るボタンを作る完全ガイド / jQueryでスクロールで追従するヘッダーの実装完全ガイド

よくある質問(FAQ)

Qスクロールしても最下部に「到達しない」と判定される
Aブラウザのズームや小数点スクロールの誤差が原因です。scrollTop + innerHeight >= scrollHeight - 2 のように2px程度のマージンを設けると解決します。
Qコンテナ内スクロールとウィンドウスクロールの違いは?
Aコンテナ内スクロールは overflow-y: scroll のついたボックス内の移動で、$box.scrollTop()$box[0].scrollHeight で測定します。ウィンドウスクロールは $(window).scrollTop()$(document).height() を使います。
Qスクロール率(何%読んだか)を表示するには?
AMath.round(scrollTop / (scrollHeight - innerHeight) * 100) でパーセンテージを計算できます。これをプログレスバーの width に反映させると視覚的にわかりやすくなります(セクション3参照)。
Qスクロールイベントが重くなる場合は?
Aスクロールイベントは非常に頻繁に発火します。ボタンが活性化されたら $(element).off('scroll') でイベントを解除することで、不要な処理を止めてパフォーマンスを改善できます(セクション5参照)。
Q利用規約が短くてスクロールが発生しない場合は?
A$box[0].scrollHeight <= $box.innerHeight() でスクロール不要かどうかを判定し、その場合は初めからボタンを活性化してください(セクション6参照)。