Files
BigStickNewOfficialWebsite/components/AnimatedStat.vue
T
2026-07-10 18:23:41 +08:00

70 lines
1.4 KiB
Vue

<script setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
const props = defineProps({
label: {
type: String,
required: true,
},
value: {
type: Number,
required: true,
},
suffix: {
type: String,
default: "",
},
});
const elRef = ref(null);
const state = reactive({ value: 0 });
let animation;
let trigger;
const displayValue = computed(() => Math.round(state.value));
onMounted(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
state.value = props.value;
return;
}
const { $gsap, $ScrollTrigger } = useNuxtApp();
if (!elRef.value || !$gsap || !$ScrollTrigger) {
state.value = props.value;
return;
}
trigger = $ScrollTrigger.create({
trigger: elRef.value,
start: "top 85%",
once: true,
onEnter: () => {
animation = $gsap.to(state, {
value: props.value,
duration: 1.4,
ease: "power2.out",
});
},
});
});
onBeforeUnmount(() => {
animation?.kill?.();
trigger?.kill?.();
});
</script>
<template>
<div
ref="elRef"
class="rounded-lg border border-gm-border bg-white p-6 text-center shadow-sm"
>
<div class="text-4xl font-semibold text-gm-green">
{{ displayValue }}<span>{{ suffix }}</span>
</div>
<p class="mt-2 text-sm text-[#777]">{{ label }}</p>
</div>
</template>