【GSAP】onUpdate で進捗を取得!スクロール連動の円形プログレスバーを作る方法
Webサイトをスクロールしている際、画面の隅に「現在ページのどのあたりまで読み進めているか」を示すプログレスバー(インジケーター)を見たことはありませんか?
今回は、GSAPの ScrollTrigger が持つ onUpdate コールバック と progress(進捗度) を活用して、ページ全体のスクロールに連動する「円形のプログレスバー」を実装する方法をご紹介します!
GSAPの応用的な使い方に加え、SVGの <circle> 要素とCSSの stroke-dasharray を組み合わせた円形プログレスバーの作り方がメインの解説になります。コピペで使えるデモも用意したので、ぜひ参考にしてみてください!
デモ動作
See the Pen GSAP onUpdateで作る円形プログレスバー by mikanbako (@mikanbako) on CodePen.
demo: GSAP onUpdateで作る円形プログレスバー
実装コードの紹介
【HTML】
<!-- スクロールを発生させるためのラッパー要素 -->
<div class="l-wrap js-wrap">
<div class="l-spacer">
<p>↓ スクロールしてね ↓</p>
</div>
<div class="l-spacer">
<p>↓ まだまだ続くよ ↓</p>
</div>
<div class="l-spacer">
<p>↑ ゴール! ↑</p>
</div>
</div>
<!-- 画面左下に固定するプログレスバー -->
<div class="p-progress">
<svg class="p-progress__svg" viewBox="0 0 100 100">
<!-- 背景となる薄い円 -->
<circle class="p-progress__bg" cx="50" cy="50" r="44" />
<!-- スクロールに合わせて伸びる円 -->
<circle class="p-progress__bar js-progress-bar" cx="50" cy="50" r="44" />
</svg>
<!-- 中央に表示するパーセンテージ -->
<div class="p-progress__text">
<span class="js-progress-text">0</span>%
</div>
</div>
【CSS】
.l-spacer {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
font-weight: bold;
color: #888;
border-bottom: 1px dashed #ccc;
}
/* =========================================
プログレスバーの装飾(左下に固定)
========================================= */
.p-progress {
position: fixed;
bottom: 30px;
left: 30px;
width: 80px;
aspect-ratio: 1;
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 50%;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.p-progress__svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* 円のスタート位置を12時の方向にするため -90度回転 */
transform: rotate(-90deg);
}
/* 円の共通スタイル */
.p-progress__bg,
.p-progress__bar {
fill: none;
stroke-width: 6;
}
/* 背景の円 */
.p-progress__bg {
stroke: #eee;
}
/* 伸びる円 */
.p-progress__bar {
stroke: #1bb4d4;
/* stroke-linecap: round; で先端を丸くする */
stroke-linecap: round;
}
/* テキスト */
.p-progress__text {
position: relative;
font-size: 16px;
font-weight: bold;
color: #1bb4d4;
z-index: 2;
}
【JavaScript】
gsap.registerPlugin(ScrollTrigger);
const wrap = document.querySelector('.js-wrap');
const bar = document.querySelector('.js-progress-bar');
const text = document.querySelector('.js-progress-text');
// 1. SVGの円周の長さを計算する
// 半径(r)を取得
const radius = bar.r.baseVal.value;
// 円周の長さ = 2 * π * r
const circumference = 2 * Math.PI * radius;
// 2. SVG円の初期設定
// 破線の長さを円周と同じにする
bar.style.strokeDasharray = circumference;
// オフセット(破線の開始位置)を円周と同じにして、最初は見えない状態にする
bar.style.strokeDashoffset = circumference;
// 3. ScrollTriggerの作成
ScrollTrigger.create({
trigger: wrap,
start: "top top",
end: "max", // ページ(または要素)の最後まで
onUpdate: (self) => {
// self.progress は 0 〜 1 の間の数値
const progress = self.progress;
// ① テキストの更新(0〜100の整数に変換)
text.textContent = Math.round(progress * 100);
// ② 円形プログレスバーの更新
// スクロールが進むにつれてオフセットを減らし、円を描画する
bar.style.strokeDashoffset = circumference - (progress * circumference);
}
});
コードの解説・こだわったポイント
end: "max" でページ全体を監視
// ScrollTriggerの作成
ScrollTrigger.create({
trigger: wrap,
start: "top top",
end: "max", // ページ(または要素)の最後まで
...
});
ページ全体(あるいは特定の長いラッパー要素)のスクロール率を取得したい場合、ScrollTriggerの end プロパティに "max" を指定するのが非常に便利です。これにより、要素の高さが動的に変わっても、自動的に最大スクロール量を計算してくれます。
円形プログレスバーを描画する仕組み(SVGの計算)
// circle要素
const bar = document.querySelector('.js-progress-bar');
// 1. SVGの円周の長さを計算する
// 半径(r)を取得
const radius = bar.r.baseVal.value;
// 円周の長さ = 2 * π * r
const circumference = 2 * Math.PI * radius;
// 2. SVG円の初期設定
// 破線の長さを円周と同じにする
bar.style.strokeDasharray = circumference;
// オフセット(破線の開始位置)を円周と同じにして、最初は見えない状態にする
bar.style.strokeDashoffset = circumference;
この実装の肝となるのが、SVGの stroke-dasharray(破線の長さ)と stroke-dashoffset(破線の開始位置)を使ったテクニックです。 円周の長さは数学の公式 2πr(2 × 円周率 × 半径)で求められます。 JavaScript側で <circle> 要素から半径(r="44")を取得し、計算した円周の長さを stroke-dasharray に設定します。
onUpdate と progress で状態を同期
onUpdate: (self) => {
// self.progress は 0 〜 1 の間の数値
const progress = self.progress;
// ① テキストの更新(0〜100の整数に変換)
text.textContent = Math.round(progress * 100);
// ② 円形プログレスバーの更新
// スクロールが進むにつれてオフセットを減らし、円を描画する
bar.style.strokeDashoffset = circumference - (progress * circumference);
}
ScrollTrigger.create() 内で onUpdate コールバックを使用すると、スクロールが発生するたびに処理を実行できます。 引数として渡ってくる self オブジェクトの中には、現在の進捗度を示す self.progress(0から1の小数)が含まれています。
・テキストMath.round(self.progress * 100) で0〜100の整数に変換して表示。
・円形プログレスバー円周 - (進捗度 * 円周) を計算し、stroke-dashoffset の値を減らしていくことで、円が徐々に描画されるアニメーションを実現しています。
補足:公式ドキュメント
より詳しい仕様や最新の対応状況については、以下の公式ドキュメントをご参照ください。
まとめ
今回は、GSAPの onUpdate とSVGを組み合わせた円形プログレスバーの実装方法をご紹介しました。
GSAPは単に要素を動かすだけでなく、progress などの値を活用することで、スクロールに完全に同期した自由度の高いUIを構築することができます。SVGの制御(stroke-dashoffset)の仕組みを理解しておくと、ローディングアニメーションなど他の表現にも応用できるため、ぜひマスターしておきたいテクニックです!