diff --git a/packages/hooks/src/use-count-down.ts b/packages/hooks/src/use-count-down.ts index bfad064b..4f95b731 100644 --- a/packages/hooks/src/use-count-down.ts +++ b/packages/hooks/src/use-count-down.ts @@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue'; import { useRafFn } from '@vueuse/core'; /** - * count down + * A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing, + * independent of the screen refresh rate. * - * @param seconds - count down seconds + * @param initialSeconds - The total number of seconds for the countdown. */ -export default function useCountDown(seconds: number) { - const FPS_PER_SECOND = 60; +export default function useCountDown(initialSeconds: number) { + const remainingSeconds = ref(0); - const fps = ref(0); + const count = computed(() => Math.ceil(remainingSeconds.value)); - const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND)); - - const isCounting = computed(() => fps.value > 0); + const isCounting = computed(() => remainingSeconds.value > 0); const { pause, resume } = useRafFn( - () => { - if (fps.value > 0) { - fps.value -= 1; - } else { + ({ delta }) => { + // delta: milliseconds elapsed since the last frame. + + // If countdown already reached zero or below, ensure it's 0 and stop. + if (remainingSeconds.value <= 0) { + remainingSeconds.value = 0; + pause(); + return; + } + + // Calculate seconds passed since the last frame. + const secondsPassed = delta / 1000; + remainingSeconds.value -= secondsPassed; + + // If countdown has finished after decrementing. + if (remainingSeconds.value <= 0) { + remainingSeconds.value = 0; pause(); } }, - { immediate: false } + { immediate: false } // The timer does not start automatically. ); - function start(updateSeconds: number = seconds) { - fps.value = FPS_PER_SECOND * updateSeconds; + /** + * Starts the countdown. + * + * @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds` + */ + function start(updatedSeconds: number = initialSeconds) { + remainingSeconds.value = updatedSeconds; resume(); } + /** Stops the countdown and resets the remaining time to 0. */ function stop() { - fps.value = 0; + remainingSeconds.value = 0; pause(); } + // Ensure the rAF loop is cleaned up when the component is unmounted. onScopeDispose(() => { pause(); });