【GSAP】onUpdate で進捗を取得!スクロール連動の円形プログレスバーを作る方法

Webサイトをスクロールしている際、画面の隅に「現在ページのどのあたりまで読み進めているか」を示すプログレスバー(インジケーター)を見たことはありませんか?

今回は、GSAPの ScrollTrigger が持つ onUpdate コールバックprogress(進捗度) を活用して、ページ全体のスクロールに連動する「円形のプログレスバー」を実装する方法をご紹介します!

GSAPの応用的な使い方に加え、SVGの <circle> 要素とCSSの stroke-dasharray を組み合わせた円形プログレスバーの作り方がメインの解説になります。コピペで使えるデモも用意したので、ぜひ参考にしてみてください!

デモ動作

See the Pen GSAP onUpdateで作る円形プログレスバー by mikanbako (@mikanbako) on CodePen.

全画面でのデモ動作は以下のリンクからご確認下さい!(別タブで開かれます)

実装コードの紹介

【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 に設定します。

onUpdateprogress で状態を同期

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: ScrollTrigger progress

まとめ

今回は、GSAPの onUpdate とSVGを組み合わせた円形プログレスバーの実装方法をご紹介しました。

GSAPは単に要素を動かすだけでなく、progress などの値を活用することで、スクロールに完全に同期した自由度の高いUIを構築することができます。SVGの制御(stroke-dashoffset)の仕組みを理解しておくと、ローディングアニメーションなど他の表現にも応用できるため、ぜひマスターしておきたいテクニックです!

Prev
Next