use-count-down.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import { computed, onScopeDispose, ref } from 'vue';
  2. import { useRafFn } from '@vueuse/core';
  3. /**
  4. * A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing,
  5. * independent of the screen refresh rate.
  6. *
  7. * @param initialSeconds - The total number of seconds for the countdown.
  8. */
  9. export default function useCountDown(initialSeconds: number) {
  10. const remainingSeconds = ref(0);
  11. const count = computed(() => Math.ceil(remainingSeconds.value));
  12. const isCounting = computed(() => remainingSeconds.value > 0);
  13. const { pause, resume } = useRafFn(
  14. ({ delta }) => {
  15. // delta: milliseconds elapsed since the last frame.
  16. // If countdown already reached zero or below, ensure it's 0 and stop.
  17. if (remainingSeconds.value <= 0) {
  18. remainingSeconds.value = 0;
  19. pause();
  20. return;
  21. }
  22. // Calculate seconds passed since the last frame.
  23. const secondsPassed = delta / 1000;
  24. remainingSeconds.value -= secondsPassed;
  25. // If countdown has finished after decrementing.
  26. if (remainingSeconds.value <= 0) {
  27. remainingSeconds.value = 0;
  28. pause();
  29. }
  30. },
  31. { immediate: false } // The timer does not start automatically.
  32. );
  33. /**
  34. * Starts the countdown.
  35. *
  36. * @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds`
  37. */
  38. function start(updatedSeconds: number = initialSeconds) {
  39. remainingSeconds.value = updatedSeconds;
  40. resume();
  41. }
  42. /** Stops the countdown and resets the remaining time to 0. */
  43. function stop() {
  44. remainingSeconds.value = 0;
  45. pause();
  46. }
  47. // Ensure the rAF loop is cleaned up when the component is unmounted.
  48. onScopeDispose(() => {
  49. pause();
  50. });
  51. return {
  52. count,
  53. isCounting,
  54. start,
  55. stop
  56. };
  57. }