どうも、くまだです。
JavaScriptを使ったスムーススクロールの解説。自分なりの解釈やメモになります。
JavaScriptを使ったスムーススクロール
HTMLは以下のようになります。(CSSは今回関係ないので省略)
<div class="l-container">
<header class="l-header p-header">
<div class="p-header__inner">
<h1 class="p-header__title">
<a href="/">
<img src="" alt="" width="138" height="36">
</a>
</h1>
<div class="p-header__hamburger">
<button type="button" class="c-hamburger js-hamburger" aria-controls="navigation" aria-expanded="false"
aria-label="メニューを開く">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="p-header__nav js-nav-area" id="navigation">
<nav class="p-nav" id="js-global-navigation" aria-label="グローバルナビゲーション">
<div class="p-nav__inner">
<ul class="p-nav__list">
<li class="p-nav__item">
<a href="#section-a" class="p-nav__link">a</a>
</li>
<li class="p-nav__item">
<a href="#section-b" class="p-nav__link">b</a>
</li>
<li class="p-nav__item">
<a href="#section-c" class="p-nav__link">c</a>
</li>
<li class="p-nav__item">
<a href="#section-d" class="p-nav__link">d</a>
</li>
<li class="p-nav__item">
<a href="#section-e" class="p-nav__link">e</a>
</li>
</ul>
</div>
</nav>
</div>
</div>
</header>
<main class="p-main">
<section class="p-section" style="background-color: red;" id="section-a">
<h2>セクションa</h2>
</section>
<section class="p-section" style="background-color: blue" id="section-b">
<h2>セクションb</h2>
</section>
<section class="p-section" style="background-color: green;" id="section-c">
<h2>セクションc</h2>
</section>
<section class="p-section" style="background-color: orange;" id="section-d">
<h2>セクションd</h2>
</section>
<section class="p-section" style="background-color: yellow;" id="section-e">
<h2>セクションe</h2>
</section>
</main>
<footer>
<ul class="p-nav__list">
<li class="p-nav__item">
<a href="#section-a" class="p-nav__link">a</a>
</li>
<li class="p-nav__item">
<a href="#section-b" class="p-nav__link">b</a>
</li>
<li class="p-nav__item">
<a href="#section-c" class="p-nav__link">c</a>
</li>
<li class="p-nav__item">
<a href="#section-d" class="p-nav__link">d</a>
</li>
<li class="p-nav__item">
<a href="#section-e" class="p-nav__link">e</a>
</li>
</ul>
</footer>
</div>
document.addEventListener('DOMContentLoaded', function () {
const smoothScrollTrigger = document.querySelectorAll('a[href^="#"]');
function smoothScroll(targetId) {
const targetElement = document.getElementById(targetId);
if (targetElement) {
const header = document.querySelector('.p-header');
const headerHeight = header.clientHeight;
const targetRect = targetElement.getBoundingClientRect().top;
const offset = window.scrollY;
const target = targetRect + offset - headerHeight;
window.scrollTo({
top: target,
behavior: 'smooth',
});
}
}
smoothScrollTrigger.forEach(trigger => {
trigger.addEventListener('click', function (e) {
e.preventDefault();
const href = trigger.getAttribute('href').replace('#', '');
smoothScroll(href);
});
});
});
JavaScriptの解説
以下の記述で、href属性が#で始まるaタグを全て取得します。
const smoothScrollTrigger = document.querySelectorAll('a[href^="#"]');
以下のループで、各aタグにクリックイベントを追加します。
foreach文は、
- (配列).foreach((各要素) => (各要素に対して行う関数))
のような記述になります(なお、querySelectorAllの返り値はは配列ではない)。
これを当てはめると、以下のようになります。
smoothScrollTrigger.forEach(trigger => {
trigger.addEventListener('click', function () {
// ここにクリックイベント書く
});
});
クリックイベント内には以下のように記述します。
getAttributeでhref属性の値を取得します。例えば、<a href=”#section-b”>をクリックしたら「#section-b」が取得されます。
これを、replace(‘#’, ”)で「#」文字を削除します。つまり取得した「#section-b」の「#」が取り除かれて「section-b」になり、それを変数「href」に格納します。
その変数を後述するsmoothScroll関数に渡します。
smoothScrollTrigger.forEach(trigger => {
trigger.addEventListener('click', function () {
const href = trigger.getAttribute('href').replace('#', '');
smoothScroll(href);
});
});
smoothScroll関数作成
smoothScroll関数は以下のように記述していきます。
smoothScroll関数の()内には「href」が渡されます。ここでは「targetId」として受け取ります。
function smoothScroll(targetId) { // targetIdに変数hrefが渡される
// ここに処理を書く
const targetElement = document.getElementById(targetId);
}
イメージとしては、下のような状態です。<a href=”#section-b”>をクリックした場合は、変数hrefの中には「section-b」が格納されています。
function smoothScroll(href) {
// ここに処理を書く
const targetElement = document.getElementById(href);
}
// ↓ <a href="#section-b">をクリックした場合
function smoothScroll(section-b) {
const targetElement = document.getElementById(section-b);
}
getElementById()でそのidを持つ要素を取得します。それを変数targetElementに格納します。
次に、もしその要素が存在するならif文以下の処理になります。
- p-header要素を取得して変数headerに格納
- 変数headerの「paddingのみを含んだ高さ」をclientHeihtで取得
- 変数targetElementの「ビューポートに対する相対位置」をgetBoundingClientRect().topで取得して変数targetRectに格納
- 変数offsetにwindow.scrollYで取得した「現在のスクロール位置」を格納
function smoothScroll(targetId) {
const targetElement = document.getElementById(targetId);
if (targetElement) { // もしtargetElement が存在するなら
const header = document.querySelector('.p-header');
const headerHeight = header.clientHeight;
const targetRect = targetElement.getBoundingClientRect().top;
const offset = window.scrollY;
const target = targetRect + offset - headerHeight;
}
}
イメージとしては以下のような感じです。

変数targetには、
- ページのスクロール量+ビューポートから要素までの距離
を格納しています。固定ヘッダーがある場合は、変数headerを高さを引いてあげます。
次にwindow.scrollTo()でウィンドウを指定した位置にスクロールさせます。topにはスクロール先の縦方向のピクセル位置を指定します。ここでは、変数targetを指定します。
behaviorオプションのsmooth指定でスムーススクロールになります。
window.scrollTo({
top: target,
behavior: 'smooth',
});
現状だと、aタグをクリックすると一瞬で該当のセクションに移動するので、以下の処理を追記します。e.preventDefault();がないと、デフォルトのページジャンプが起こり、スムーススクロールが行われません。
e.preventDefault();を追記するとそのデフォルトのページジャンプ動作を防ぐことができます。デフォルトのページジャンプを無効化して、smoothScroll関数でスムーススクロールさせるイメージです。
smoothScrollTrigger.forEach(trigger => {
trigger.addEventListener('click', function (e) { // e追記
e.preventDefault(); // 追記
const href = trigger.getAttribute('href').replace('#', '');
smoothScroll(href);
});
});
ここまで読んでくださりありがとうございました。