この記事では
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>
完成品
このブログのページトップボタンに同じ実装をしたプログレスバーがついています。
脚注
これらのAPIを使った処理は実DOMを参照する関係上、マウント後に行います。処理はすべて
onMounted
フックのコールバックに含めます。 ↩︎