プログラミング
プログラミング
  • 2024/03/30
  • 2024/09/17

Vueでスクロールに同期したプログレスバーを実装する

Vue.jsを使ってスクロールに同期した円形プログレスバーを実装しました。

この記事では script setup 構文を用いています。

参考


方針


参考記事のようにSVGを使って実装します。
Vue3 script setupでは <script> で定義した変数を <style> 内のCSSにvindすることができます。これを利用することで、スクロールに同期してステートを更新すると同時に、CSSの stroke-dashoffset にその値をbindすることで実現しようという考えです。

実装


<circle> の半径を とするとき、その円周の長さ circumference)は、

で計算されます。これに対し、 だけ進んだ時のプログレスバーの弧の長さ progress)は、

で計算できます。progressPercent というステートとしてリアクティブに宣言し、computed フックを用いることで の値の更新を拾って勝手に再計算されるようにしています。
はJSのWindow APIおよびDocument APIを使ってスクロール量および画面高さを取得[1]して計算します。

<script setup lang="ts">
import { onMounted, computed, ref } from "vue";

// Progress circle
const radius = 42;

const progressPercent = ref<number>(0);

const circumference = 2 * Math.PI * radius;

const progress = computed<number>(() => {
  const val = 2 * Math.PI * (1 - progressPercent.value) * radius;
  if (progressPercent.value > 1) {
    return 0;
  }
  return val;
});

onMounted(() => {
  progressPercent.value = window.scrollY / document.body.scrollHeight;
  const height = document.body.scrollHeight;
  window.addEventListener("scroll", () => {
    const scrollAmount = window.scrollY;
    progressPercent.value = scrollAmount / height;
  });
})
</script>

<template>
  <div class="progress-wrapper">
    <svg class="progress-bar" viewBox="0 0 100 100">
      <circle class="bar" cx="50" cy="50" :r="radius"></circle>
      <circle class="bg" cx="50" cy="50" :r="radius"></circle>
    </svg>
  </div>
</template>

<style scoped lang="scss">
.progress-wrapper {
  width: 42px;
  height: 42px;

  .bg {
    stroke: #ffffff;
    fill: #ffffff;
  }

  .bar {
    stroke-linecap: butt;
    stroke-width: 16px;
    stroke: $rose;   // #fb7185
    stroke-dasharray: v-bind(circumference);
    stroke-dashoffset: v-bind(progress);
  }
}
</style>

完成品


このブログのページトップボタンに同じ実装をしたプログレスバーがついています。

脚注


  1. これらのAPIを使った処理は実DOMを参照する関係上、マウント後に行います。処理はすべて onMounted フックのコールバックに含めます。 ↩︎

関連記事


Shota Inoue
Shota Inoue

大学生 | 化学・Webプログラミング・統計学など