「ページの最下部まで読んだら次のコンテンツへ自動遷移」「CTA要素が画面内に入ったら詳細ページへ誘導」など、スクロール位置をトリガーにしたページ遷移はコンテンツ誘導やシングルページ型体験の演出に使われます。
この記事ではスクロール量(scrollY)による閾値判定、要素への到達を検知するIntersectionObserver方式、一度だけ発火する仕組み、遅延カウントダウン遷移、ユーザーへの通知UIとキャンセル対応まで体系的に解説します。
基本:scrollY で閾値を判定してページ遷移
window.scrollY がある値を超えたら location.href でページを遷移させる、最もシンプルな実装です。
window.addEventListener('scroll', () => {
// ページ上部から500px スクロールしたら遷移
if (window.scrollY >= 500) {
window.location.href = 'https://example.com/next-page/';
}
});
- scroll イベントは高頻度で発火するため、条件を満たすたびに何度もリダイレクトが試みられます(ブラウザは最初の1回で遷移しますが無駄な処理が発生)
- スクロールするたびにチェックが走り続けるためパフォーマンスに影響します
- 一度遷移が始まったらキャンセルできません
次のセクションでこれらを解決した実装を紹介します。
改善版:一度だけ発火させる
フラグ変数または { once: true } オプションを使い、条件を満たした後はリスナーを解除します。
function setupScrollRedirect(threshold, url) {
function onScroll() {
if (window.scrollY >= threshold) {
// リスナーを削除して以降は発火させない
window.removeEventListener('scroll', onScroll);
window.location.href = url;
}
}
window.addEventListener('scroll', onScroll, { passive: true });
}
// ページを500px スクロールしたら遷移
setupScrollRedirect(500, 'https://example.com/next-page/');
{ passive: true } を指定する理由:scroll イベントリスナーに passive: true を指定すると、ブラウザがスクロールのデフォルト動作をブロックしないと判断してスクロール処理を最適化できます。遷移処理は preventDefault() を呼ばないので、必ず passive: true を付けましょう。ページ末尾まで読んだら遷移
「記事を最後まで読んだ」タイミングを検知するには、現在のスクロール位置と全体の高さを比較します。
function setupBottomRedirect(url, marginPx = 100) {
function onScroll() {
const scrolled = window.scrollY + window.innerHeight;
const totalHeight = document.documentElement.scrollHeight;
// 末尾から marginPx 以内に到達したら遷移
if (scrolled >= totalHeight - marginPx) {
window.removeEventListener('scroll', onScroll);
window.location.href = url;
}
}
window.addEventListener('scroll', onScroll, { passive: true });
}
setupBottomRedirect('https://example.com/next-article/', 100);
window.scrollY:ページ上部からスクロールした量(px)window.innerHeight:ビューポートの高さ(px)document.documentElement.scrollHeight:ページ全体の高さ(px)scrollY + innerHeight:現在見えている範囲の下端の位置
IntersectionObserver で特定要素への到達を検知
「フッターが画面内に入ったら遷移」「CTA要素が表示されたら遷移」など、特定の要素が画面に入ったタイミングで遷移させたい場合は IntersectionObserver が最も適切です。scroll イベントより効率的で、閾値計算も不要です。
/**
* 指定した要素が画面内に入ったらページ遷移する
* @param {string} selector 監視する要素のセレクター
* @param {string} url 遷移先 URL
* @param {number} threshold 0〜1(要素の何割が見えたら発火するか)
*/
function setupElementRedirect(selector, url, threshold = 0.5) {
const target = document.querySelector(selector);
if (!target) return;
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
observer.disconnect(); // 一度だけ発火して監視終了
window.location.href = url;
}
}, { threshold });
observer.observe(target);
}
// フッターが50%以上見えたら遷移
setupElementRedirect('#page-footer', 'https://example.com/next/', 0.5);
// 記事末尾の「次の記事へ」要素が完全に表示されたら遷移
setupElementRedirect('#next-article-trigger', 'https://example.com/article/2/', 1.0);
遅延遷移:カウントダウンUIとキャンセル対応
突然のページ遷移はユーザーを驚かせます。実務では「3秒後に自動遷移します」のようなカウントダウンUIと、キャンセルボタンを提供するのがUXのベストプラクティスです。
<!-- 遷移通知バナー(通常は非表示) -->
<div id="redirect-banner" hidden style="
position: fixed; bottom: 0; left: 0; right: 0;
background: #1e40af; color: white;
padding: 12px 20px; display: flex;
justify-content: space-between; align-items: center;
font-size: 14px; z-index: 9999;
">
<span id="redirect-message">3秒後に次のページへ移動します…</span>
<button id="cancel-redirect" style="
background: white; color: #1e40af;
border: none; padding: 6px 16px;
border-radius: 4px; cursor: pointer; font-weight: bold;
">キャンセル</button>
</div>
/**
* スクロールトリガー + カウントダウン + キャンセル可能な遷移
*/
function setupCountdownRedirect({ selector, url, delay = 3, threshold = 0.5 }) {
const target = document.querySelector(selector);
const banner = document.getElementById('redirect-banner');
const message = document.getElementById('redirect-message');
const cancelBtn = document.getElementById('cancel-redirect');
if (!target || !banner) return;
let timerId = null;
let cancelled = false;
function startCountdown() {
let remaining = delay;
banner.hidden = false;
message.textContent = `${remaining}秒後に次のページへ移動します…`;
timerId = setInterval(() => {
remaining--;
if (remaining <= 0) {
clearInterval(timerId);
if (!cancelled) window.location.href = url;
} else {
message.textContent = `${remaining}秒後に次のページへ移動します…`;
}
}, 1000);
}
// キャンセルボタン
cancelBtn.addEventListener('click', () => {
cancelled = true;
clearInterval(timerId);
banner.hidden = true;
observer.disconnect(); // 再発火させない
});
// IntersectionObserver で要素到達を監視
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
observer.disconnect();
startCountdown();
}
}, { threshold });
observer.observe(target);
}
// 使用例
setupCountdownRedirect({
selector: '#article-end',
url: 'https://example.com/next-article/',
delay: 5, // 5秒後に遷移
threshold: 0.8,
});
role="alert" と aria-live="assertive" を付けると、スクリーンリーダーが「3秒後に遷移します」とユーザーに読み上げます。キャンセルボタンは autofocus を付けてキーボード操作できるようにしましょう。location.href と location.replace の使い分け
URLを変える方法が複数あります。「戻るボタンで元のページに戻れるようにするか」が選択の基準です。
// ① location.href(推奨)
// 履歴に残るので「戻る」ボタンで元ページに戻れる
window.location.href = 'https://example.com/next/';
// ② location.replace
// 履歴に残らない。「戻る」で元ページには戻れない
// 自動ログインリダイレクトなど「戻られたくない」場合に使う
window.location.replace('https://example.com/next/');
// ③ history.pushState(SPA用)
// URL は変わるがページ遷移しない。SPA でのルーティングに使う
history.pushState({}, '', '/next-page');
| 方法 | 「戻る」ボタン | ページリロード | 用途 |
|---|---|---|---|
location.href |
元ページに戻れる | あり | 一般的な遷移 |
location.replace |
戻れない | あり | リダイレクト・ログイン後 |
history.pushState |
戻れる(SPA内) | なし | SPA ルーティング |
スクロール量をパーセントで計算して遷移
「ページの80%を読んだら遷移」のように、絶対的なpx値ではなくパーセントで閾値を設定できます。
/**
* スクロール量のパーセントでリダイレクトをトリガー
* @param {number} percent 0〜100(例: 80 = 80%スクロールしたら)
* @param {string} url 遷移先 URL
*/
function setupPercentRedirect(percent, url) {
function onScroll() {
const scrolled = window.scrollY + window.innerHeight;
const total = document.documentElement.scrollHeight;
const ratio = (scrolled / total) * 100;
if (ratio >= percent) {
window.removeEventListener('scroll', onScroll);
window.location.href = url;
}
}
window.addEventListener('scroll', onScroll, { passive: true });
}
// ページの80%をスクロールしたら遷移
setupPercentRedirect(80, 'https://example.com/next/');
scroll イベントのデバウンスでパフォーマンス改善
scroll イベントは1秒間に数十〜数百回発火します。複雑な計算をする場合はデバウンス(一定時間内の連続呼び出しをまとめる)を使います。
function debounce(fn, wait) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), wait);
};
}
const checkScroll = debounce(() => {
if (window.scrollY >= 500) {
window.location.href = 'https://example.com/next/';
}
}, 50); // 50ms ごとに1回だけ実行
window.addEventListener('scroll', checkScroll, { passive: true });
よくある質問(FAQ)
window.pageYOffset は古い名称で、現在は window.scrollY が標準です。IE 非対応で問題なければ scrollY を使いましょう。sessionStorage にフラグを保存して、一度遷移が発火したら再発火しないようにします。if (sessionStorage.getItem("redirected")) return;遷移前に
sessionStorage.setItem("redirected", "1") を実行しておけば、戻ってきても再遷移しません。document.documentElement.scrollTop が 0 を返す場合があります。window.scrollY ?? document.documentElement.scrollTop ?? document.body.scrollTop のようにフォールバックするか、IntersectionObserver を使うとブラウザ差異を気にせず実装できます。まとめ
スクロールトリガーのページ遷移実装を用途別にまとめます。
| 用途 | 推奨手法 |
|---|---|
| px 単位の閾値で遷移 | scroll イベント + scrollY 比較 + リスナー削除 |
| ページ末尾到達で遷移 | scrollY + innerHeight >= scrollHeight - margin |
| 特定要素が画面内に入ったら遷移 | IntersectionObserver(推奨・高効率) |
| カウントダウン + キャンセル付き遷移 | setInterval カウントダウン + キャンセルボタン |
| スクロール率(%)で遷移 | (scrollY + innerHeight) / scrollHeight * 100 |
| 「戻る」で再遷移を防ぐ | sessionStorage にフラグ保存 |
スクロールイベントの監視についてはIntersectionObserverでスクロールイベントを設定する方法を、URL遷移の方法全般についてはJavaScriptでURLを遷移させる方法もあわせて参照してください。
