【JavaScript】「ページ内スクロール」と「トップに戻る」を同時に実装する方法
今回は「ページ内スクロール」と「トップへ戻る」を同時に実装する方法を説明します。
前提条件は以下
- jQueryは使わない
- リンクが#のみだと画面トップまで戻る
- リンクが設定されていても、スクロール対象が見つからないときはアラート
- 固定ヘッダーがある場合、その高さ分、スクロール位置を調整できる(PCとSPで切り替え可能)
説明環境は以下
- macOS Monterey 12.5
- Visual Studio Code v1.70.1
ページ内スクロールとトップに戻るを同時に実装する
最近ではスムーススクロールはCSSだけで実装可能です。以下のようにhtml要素にscroll-behaviorを指定すれば実装できます。固定ヘッダーがある場合もscroll-margin-topで高さ分、スクロール位置が調整できます。
html{
scroll-behavior: smooth;
scroll-margin-top: 60px; //固定ヘッダーの高さ
}
ただし、別ページの特定の場所に飛ぶときも、まずは別ページの画面トップに飛んでからスムーススクロールで特定の場所に移動する動きになるので、個人的にはJavaScriptで実装するようにしています。
HTMLで書いてること
以下はデモページのHTMLです。
<div class="p-scroll">
<nav class="p-scroll__nav">
<ul class="p-scroll__list">
<li class="p-scroll__item"><a class="p-scroll__link" href="#section01">section1</a></li>
<li class="p-scroll__item"><a class="p-scroll__link" href="#section02">section2</a></li>
<li class="p-scroll__item"><a class="p-scroll__link" href="#section03">section3</a></li>
<li class="p-scroll__item"><a class="p-scroll__link" href="#section04">section4</a></li>
<li class="p-scroll__item"><a class="p-scroll__link" href="#section05">section5</a></li>
</ul>
</nav>
<section class="p-scroll__section p-scroll__section--01" id="section01"><span class="p-scroll__section-text">SECTION1</span></section>
<section class="p-scroll__section p-scroll__section--02" id="section02"><span class="p-scroll__section-text">SECTION2</span></section>
<section class="p-scroll__section p-scroll__section--03" id="section03"><span class="p-scroll__section-text">SECTION3</span></section>
<section class="p-scroll__section p-scroll__section--04" id="section04"><span class="p-scroll__section-text">SECTION4</span></section>
</div>
<div class="p-scroll__top"><a class="p-scroll__top-link" href="#">↑</a></div>
<div class="p-scroll__footer"><span class="p-scroll__footer-text">FOOTER</span></div>
</div>
ナビゲーションをクリックすれば、各sectionにスムーススクロールします。ナビゲーションにあるsection5については、section5自体が存在しないので「移動先が見当たりません」とalertされます。
デモページ右下にある上向き矢印をクリックすれば、ページ上までスムーススクロールします。このように「トップに戻る」を機能させる場合は、リンクには#(ハッシュタグ)のみ設定してください。
JavaScriptで書くこと
以下はデモページのJavaScriptです。
const smoothScroll = (gapPC = 0, gapSP = 0) => {
const mediaSp = window.matchMedia('(max-width: 560px)');
let gap;
if (mediaSp.matches) {
gap = gapSP;
} else {
gap = gapPC;
}
const positionSet = (elem, _gap) => {
const targetElement = document.getElementById(elem.replace('#', ''));
if (!targetElement) {
return undefined;
}
const rect = targetElement.getBoundingClientRect().top;
const offset = window.pageYOffset;
const target = rect + offset - _gap;
return target;
};
// URLのハッシュ部分(#以降)を取得
const urlHash = window.location.hash;
// URLにハッシュ部分があれば…
if (urlHash) {
setTimeout(() => {
// スクロール先の要素が見つからない場合は…
if (positionSet(urlHash, gap) === undefined) {
// アラートを出す
alert('移動先が見当たりません');
// スクロール先の要素があれば…
} else {
window.scrollTo({
top: positionSet(urlHash, gap), // ハッシュ値と固定headerの余白分から移動距離を取得
// 別ページのときはスムーススクロールせずに移動
});
}
}, 0);
// URLにハッシュ部分が無ければ…
} else {
// a要素でリンク先に#が設定されている要素を全て取得
const smoothScrollTrigger = document.querySelectorAll('a[href^="#"]');
// もし要素がなければ処理終了
if (!smoothScrollTrigger.length) return;
// 取得した要素の数だけループ
for (let i = 0; i < smoothScrollTrigger.length; i = i + 1 || 0) {
// 要素1つ1つにクリックイベントを設定
smoothScrollTrigger[i].addEventListener('click', (e) => {
// クリックイベントの初期動作を停止
e.preventDefault();
// 要素からhrefに設定されている値を取得
const href = smoothScrollTrigger[i].getAttribute('href');
let targetPosition;
// hrefの値が#のみなら…
if (href === '#') {
// トップに戻すため0を設定
targetPosition = 0;
// スクロール先が見つからない場合は…
} else if (positionSet(href, gap) === undefined) {
// アラートを出す
alert('移動先が見当たりません');
/// スクロール先の要素があれば…
} else {
targetPosition = positionSet(href, gap); // ハッシュ値と固定headerの余白分から移動距離を取得
}
window.scrollTo({
top: targetPosition, // 移動距離を設定
behavior: 'smooth', // ページ内ではスムーススクロールを設定
});
});
}
}
};
smoothScroll(); //smoothScrollを実行
固定ヘッダーの高さがPC表示では60pxだった場合、以下のように書くと60px分の位置を調整して移動します。
smoothScroll(60); //60px分、位置を調整して移動する
固定ヘッダーの高さがスマホ表示では30px、PC表示では60pxの場合は以下です。
smoothScroll(60,30); //PCは60px、SPは30px分、調整して移動する
スマホ表示の判定はmatchMediaを使っています。今回は560px以下をスマホだと判定しているので、お好みでここの数値は変更してください、
const mediaSp = window.matchMedia('(max-width: 560px)'); //560px以下の場合
let gap;
if (mediaSp.matches) {
gap = gapSP;
} else {
gap = gapPC;
}
さいごに
今回はJavaScriptで「ページ内スクロール」と「トップに戻る」を同時に実装する方法を説明しました。CSSだけでスムーススクロールを実装した場合と比べて、非常に手間がかかるのがわかります。
それでもCSSのscroll-behaviorのように、別ページの特定の場所までスムーススクロールで移動するのは、読み手のストレスになりうるので、JavaScriptで実装することをオススメします。