「利用規約を最後まで読んだら同意ボタンを押せるようにする」という実装は、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=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スクロール率(何%読んだか)を表示するには?
A
Math.round(scrollTop / (scrollHeight - innerHeight) * 100) でパーセンテージを計算できます。これをプログレスバーの width に反映させると視覚的にわかりやすくなります(セクション3参照)。Qスクロールイベントが重くなる場合は?
Aスクロールイベントは非常に頻繁に発火します。ボタンが活性化されたら
$(element).off('scroll') でイベントを解除することで、不要な処理を止めてパフォーマンスを改善できます(セクション5参照)。Q利用規約が短くてスクロールが発生しない場合は?
A
$box[0].scrollHeight <= $box.innerHeight() でスクロール不要かどうかを判定し、その場合は初めからボタンを活性化してください(セクション6参照)。