固定ヘッダーに被らない!GSAP(ScrollToPlugin)で作るスムーススクロール
Webサイト制作で「ページ内リンク(アンカーリンク)」を実装したとき、移動先の見出しが上部固定(追従)ヘッダーの裏側にすっぽり隠れてしまって困った経験はありませんか?
CSSの scroll-padding-top を使ってサクッと解決することもできますが、要件によっては「もう少し細かくスクロール位置を調整したい」「移動中のアニメーションをもっと滑らかにしたい」という場面も出てきますよね。
そこで今回は、JavaScriptの強力なアニメーションライブラリである「GSAP(ScrollToPlugin)」を活用して、ヘッダーの高さに依存しない、より細やかで完璧なスムーススクロールを実装する方法をご紹介します!
実際の動作デモ
まずはGSAPを使った実際のスクロールの動きを、以下のデモページで確認してみてください。
demo: fixed-header-scroll
※リンクをクリックした際、要素がヘッダーの裏側に隠れず、上に綺麗な余白(24px)を保ったままピタッと止まる点に注目してください!
実装コードの紹介
今回のスムーススクロールを実装するための、基本的なHTML・CSS・JavaScriptのコードです。ご自身の環境に合わせてクラス名などを調整してご活用ください。
HTMLの構造
ページ上部に固定するヘッダー(ナビゲーション)と、スクロール先の各セクションを用意します。リンクは標準的なアンカーリンク(href="#ID名")の形式で記述します。
<header class="l-header">
<nav class="l-nav">
<a href="#section-1">セクション1へ</a>
<a href="#section-2">セクション2へ</a>
<a href="#section-3">セクション3へ</a>
</nav>
</header>
<main class="l-main">
<section id="section-1">
<h2>セクション1</h2>
</section>
<section id="section-2">
<h2>セクション2</h2>
</section>
<section id="section-3">
<h2>セクション3</h2>
</section>
</main>
CSSの設定
ヘッダーを position: fixed で上部に固定します。そのままではメインコンテンツの上部がヘッダーの裏に隠れてしまうため、ヘッダーの高さ分だけ padding-top を設けておくのがポイントです。
/* ヘッダーを上部に固定 */
.l-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px; /* JSで高さを取得するので、どんな値でも可 */
z-index: 100;
}
/* ヘッダーの裏にコンテンツが隠れないように余白を設ける */
.l-main {
padding-top: 60px; /* ヘッダーの高さ分下げる */
}
/* 各セクション(テスト用に高さを出しています) */
section {
min-height: 100vh;
padding: 40px 20px;
}
JavaScriptの実装
GSAP本体と ScrollToPlugin を読み込んだ上で、以下のJavaScriptを記述します。
リンクをクリックした際、デフォルトの瞬間移動をキャンセルし、GSAPのアニメーション処理へと繋ぎます。
document.addEventListener("DOMContentLoaded", () => {
// GSAP ScrollToPluginの登録
gsap.registerPlugin(ScrollToPlugin);
// ページ内リンク(#から始まるhref)をすべて取得
const anchorLinks = document.querySelectorAll('a[href^="#"]:not([href="#"])');
anchorLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault(); // デフォルトの瞬間移動をキャンセル
const targetId = link.getAttribute('href');
if (targetId && targetId !== '#') {
const targetElement = document.querySelector(targetId);
if (targetElement) {
// 1. ヘッダーの高さを動的に取得
const header = document.querySelector('.l-header');
const headerHeight = header ? header.offsetHeight : 0;
// 2. ヘッダーの高さ + 少しの余白(24px)を計算
const offset = headerHeight + 24;
// 3. GSAPで滑らかにスクロール
gsap.to(window, {
duration: 0.6,
ease: "power2.out",
scrollTo: {
y: targetElement,
offsetY: offset,
autoKill: true, // アニメーション中にユーザーが操作したら止める
}
});
}
}
});
});
});
コードの解説・こだわったポイント
ここからは、今回のJavaScript実装において特にこだわった「3つのポイント」を解説します。ただ動くだけではなく、保守性やUX(ユーザー体験)を考慮した設計にしています。
ヘッダーの高さを「動的」に取得する
固定ヘッダーの高さは、スマートフォン表示(SP)とパソコン表示(PC)で異なるケースがほとんどです。
もしJS側に固定の数値(例:60pxなど)をハードコーディングしてしまうと、後からデザイン変更でヘッダーの高さが変わった際に、スクロール位置がズレるバグの原因になってしまいます。
// 1. ヘッダーの高さを動的に取得
const header = document.querySelector('.l-header');
const headerHeight = header ? header.offsetHeight : 0;
そのため今回は offsetHeight を用いて、リンクがクリックされた瞬間の「実際のヘッダーの高さ」をJavaScriptで毎回計算して取得する設計にしています。
余白(オフセット)の追加で息苦しさをなくす
GSAPの offsetY に「ヘッダーの高さ(headerHeight)」だけを指定してスクロールを止めると、移動先の見出しテキストがヘッダーの下端にギリギリくっついてしまい、視覚的に窮屈な印象を与えてしまいます。
// 2. ヘッダーの高さ + 少しの余白(24px)を計算
const offset = headerHeight + 24;
今回は const offset = headerHeight + 24; とすることで、上に24pxの余白(デザイン上のマージン)を意図的に空けてピタッと止まるように調整し、ユーザーにとって読みやすいレイアウトを保っています。
UXを高めるautoKill: trueの設定
GSAPの ScrollToPlugin を使う最大のメリットとも言えるのが、autoKill: true というオプション設定です。
scrollTo: {
y: targetElement,
offsetY: offset,
autoKill: true, // アニメーション中にユーザーが操作したら止める
}
これは、「スムーススクロールで画面が自動移動している最中に、ユーザーが自分でマウスホイールを回したりスワイプしたり(手動スクロール)した場合、GSAPのアニメーションを強制終了してユーザーの操作を優先する」という機能です。
これがないと「画面が勝手に動いて自分の操作が効かない!」というユーザーストレス(スクロールジャック)に繋がるため、UXを向上させるために非常に重要な設定です。
まとめ
今回はGSAPの ScrollToPlugin を使って、固定ヘッダーに被らないスムーススクロールを実装する方法をご紹介しました!
CSSの scroll-padding-top だけでシンプルに完結させることもできますが、JavaScript(GSAP)を活用することで、高さを動的に計算してレイアウト崩れを防いだり、ユーザーのスクロール操作を優先(autoKill: true)できたりと、より細やかでユーザーファーストなUXを実現できます。
当サイト「Mikanbako Docs」でも実際にこの仕組みを取り入れているので、ぜひページ内のアンカーリンクでその滑らかさを体感してみてくださいね!
今後もフロントエンドのちょっとした工夫や、こだわりの実装方法を発信していくのでよろしくお願いします!