Skip to content

⏰ useCountDown - タイマー管理の神器

🌟 紹介

時間はお金です!⏰ 現代のアプリケーションでは、カウントダウン機能は至る所にあります:フラッシュセール、認証コードのカウントダウン、テスト時間、イベント終了時間... useCountDown はあなたの時間管理の専門家です!

この強力な Hook は精密なスイス時計のように、以下を提供します:

  • ⏱️ 精密な計時 - ミリ秒単位の精度、誤差ゼロ
  • 🎮 完全な制御 - 開始、一時停止、リセット、自由に操作可能
  • 📊 多様な表示 - 日、時、分、秒、ミリ秒、必要なものは全て
  • 🔔 インテリジェントコールバック - 時間の変化と終了イベント、一つも逃さない

🎯 主な機能:

  • カウントダウン管理 - 設定時間から正確にゼロまでカウントダウン
  • 🎮 状態制御 - 開始、一時停止、リセット、時間の流れを完全にコントロール
  • 📱 リアルタイム更新 - リアクティブデータ、インターフェースが自動的に同期更新
  • 🚀 パフォーマンス最適化 - スマートなレンダリング頻度、システムリソースを節約

🚀 コードデモ

⏰ 基本的な使い方 - クラシックカウントダウン

最も一般的なシナリオ:イベントカウントダウン表示

html
<template>
  <div class="countdown-demo">
    <div class="countdown-display">
      <!-- 🎨 美しいカウントダウン表示 -->
      <div class="time-block">
        <span class="time-value">{{ current.days }}</span>
        <span class="time-label">日</span>
      </div>
      <div class="time-separator">:</div>
      
      <div class="time-block">
        <span class="time-value">{{ current.hours }}</span>
        <span class="time-label">時間</span>
      </div>
      <div class="time-separator">:</div>
      
      <div class="time-block">
        <span class="time-value">{{ current.minutes }}</span>
        <span class="time-label">分</span>
      </div>
      <div class="time-separator">:</div>
      
      <div class="time-block">
        <span class="time-value">{{ current.seconds }}</span>
        <span class="time-label">秒</span>
      </div>
    </div>
    
    <!-- 📊 詳細情報表示 -->
    <div class="countdown-info">
      <p>⏱️ 総残り時間:{{ formatTime(current.total) }}</p>
      <p>📅 残り日数:{{ current.days }} 日</p>
      <p>🕐 残り時間:{{ current.hours }} 時間</p>
      <p>⏰ 残り分数:{{ current.minutes }} 分</p>
      <p>⏱️ 残り秒数:{{ current.seconds }} 秒</p>
    </div>
    
    <!-- 🎮 控制按钮 -->
    <div class="countdown-controls">
      <button @click="startCountdown" :disabled="isRunning">
        ▶️ 开始倒计时
      </button>
      <button @click="pauseCountdown" :disabled="!isRunning">
        ⏸️ 暂停倒计时
      </button>
      <button @click="resetCountdown">
        🔄 重置倒计时
      </button>
    </div>
  </div>
</template>
js
import { ref, computed } from 'vue';
import { useCountDown } from '@vant/use';

export default {
  setup() {
    const isRunning = ref(false);
    
    // ⏰ 创建倒计时实例 - 24小时倒计时
    const countDown = useCountDown({
      time: 24 * 60 * 60 * 1000, // 24小时 = 86,400,000 毫秒
      onChange: (current) => {
        // 🔔 時間が変化するたびにトリガー
        console.log('⏰ 時間更新:', {
          残り日数: current.days,
          残り時間: current.hours,
          残り分数: current.minutes,
          残り秒数: current.seconds
        });
      },
      onFinish: () => {
        // 🎉 カウントダウン終了時にトリガー
        isRunning.value = false;
        console.log('🎉 カウントダウン終了!時間です!');
        alert('⏰ 時間です!イベントは終了しました!');
      }
    });
    
    // 🎮 制御方法
    const startCountdown = () => {
      countDown.start();
      isRunning.value = true;
      console.log('▶️ カウントダウン開始!');
    };
    
    const pauseCountdown = () => {
      countDown.pause();
      isRunning.value = false;
      console.log('⏸️ カウントダウン一時停止!');
    };
    
    const resetCountdown = () => {
      countDown.reset();
      isRunning.value = false;
      console.log('🔄 カウントダウンリセット!');
    };
    
    // 🎨 時間表示のフォーマット
    const formatTime = (milliseconds) => {
      const seconds = Math.floor(milliseconds / 1000);
      const minutes = Math.floor(seconds / 60);
      const hours = Math.floor(minutes / 60);
      const days = Math.floor(hours / 24);
      
      if (days > 0) return `${days}日${hours % 24}時間`;
      if (hours > 0) return `${hours}時間${minutes % 60}分`;
      if (minutes > 0) return `${minutes}分${seconds % 60}秒`;
      return `${seconds}秒`;
    };

    return {
      current: countDown.current,
      isRunning,
      startCountdown,
      pauseCountdown,
      resetCountdown,
      formatTime
    };
  },
};

⚡ ミリ秒精度 - 高精度カウントダウン

スポーツ大会やゲームなど、極めて正確なカウントダウンが必要なシーン:

html
<template>
  <div class="precision-countdown">
    <div class="precision-display">
      <!-- 🎯 高精度表示 -->
      <div class="main-time">
        {{ current.minutes.toString().padStart(2, '0') }}:{{ current.seconds.toString().padStart(2, '0') }}
      </div>
      <div class="milliseconds">
        .{{ Math.floor(current.milliseconds / 10).toString().padStart(2, '0') }}
      </div>
    </div>
    
    <div class="precision-info">
      <p>⚡ ミリ秒精度カウントダウンデモ</p>
      <p>🎯 残りミリ秒:{{ current.milliseconds }}</p>
      <p>📊 総残り:{{ current.total }}ms</p>
    </div>
    
    <div class="precision-controls">
      <button @click="startPrecision" :disabled="isPrecisionRunning">
        ⚡ 开始高精度倒计时
      </button>
      <button @click="pausePrecision" :disabled="!isPrecisionRunning">
        ⏸️ 暂停
      </button>
      <button @click="resetPrecision">
        🔄 重置
      </button>
    </div>
  </div>
</template>
js
import { ref } from 'vue';
import { useCountDown } from '@vant/use';

export default {
  setup() {
    const isPrecisionRunning = ref(false);
    
    // ⚡ 毫秒级精度倒计时 - 5分钟
    const precisionCountDown = useCountDown({
      time: 5 * 60 * 1000, // 5分钟
      millisecond: true, // 🔧 ミリ秒レンダリングを有効にする
      onChange: (current) => {
        // ⚡ ミリ秒レベルの更新コールバック
        if (current.seconds <= 10 && current.minutes === 0) {
          console.log(`⚠️ 残り ${current.seconds}.${Math.floor(current.milliseconds / 100)} 秒!`);
        }
      },
      onFinish: () => {
        isPrecisionRunning.value = false;
        console.log('⚡ 高精度カウントダウン終了!');
        // 🎉 ここに効果や音声を追加できます
      }
    });
    
    const startPrecision = () => {
      precisionCountDown.start();
      isPrecisionRunning.value = true;
      console.log('⚡ 高精度カウントダウン開始!');
    };
    
    const pausePrecision = () => {
      precisionCountDown.pause();
      isPrecisionRunning.value = false;
      console.log('⏸️ 高精度カウントダウン一時停止!');
    };
    
    const resetPrecision = () => {
      precisionCountDown.reset();
      isPrecisionRunning.value = false;
      console.log('🔄 高精度カウントダウンリセット!');
    };

    return {
      current: precisionCountDown.current,
      isPrecisionRunning,
      startPrecision,
      pausePrecision,
      resetPrecision
    };
  },
};

🛒 実践シナリオ - フラッシュセールカウントダウン

ECサイトのフラッシュセールアクティビティの典型的なアプリケーション:

html
<template>
  <div class="seckill-countdown">
    <div class="seckill-header">
      <h3>🔥 限定タイムセール</h3>
      <div class="seckill-status" :class="{ active: isActive, ended: isEnded }">
        {{ statusText }}
      </div>
    </div>
    
    <div class="seckill-timer" v-if="!isEnded">
      <span class="timer-label">{{ timerLabel }}</span>
      <div class="timer-blocks">
        <div class="timer-block">
          <span class="timer-value">{{ current.hours.toString().padStart(2, '0') }}</span>
          <span class="timer-unit">時間</span>
        </div>
        <div class="timer-separator">:</div>
        <div class="timer-block">
          <span class="timer-value">{{ current.minutes.toString().padStart(2, '0') }}</span>
          <span class="timer-unit">分</span>
        </div>
        <div class="timer-separator">:</div>
        <div class="timer-block">
          <span class="timer-value">{{ current.seconds.toString().padStart(2, '0') }}</span>
          <span class="timer-unit">秒</span>
        </div>
      </div>
    </div>
    
    <div class="seckill-product">
      <div class="product-info">
        <h4>🎮 ゲームコントローラー Pro</h4>
          <div class="price-info">
            <span class="original-price">¥299</span>
            <span class="seckill-price">¥99</span>
            <span class="discount">期間限定7割引</span>
          </div>
      </div>
      
      <button 
        class="seckill-btn" 
        :class="{ 
          waiting: !isActive && !isEnded,
          active: isActive,
          ended: isEnded 
        }"
        :disabled="!isActive"
        @click="handleSeckill"
      >
        {{ buttonText }}
      </button>
    </div>
  </div>
</template>
js
import { ref, computed } from 'vue';
import { useCountDown } from '@vant/use';

export default {
  setup() {
    const isActive = ref(false);
    const isEnded = ref(false);
    
    // 🛒 フラッシュセールカウントダウン - 2時間
    const seckillCountDown = useCountDown({
      time: 2 * 60 * 60 * 1000, // 2時間
      onChange: (current) => {
        // 🔔 カウントダウン進行中
        if (current.total <= 60 * 1000 && !isActive.value) {
          // 最後の1分、フラッシュセールを活性化
          isActive.value = true;
          console.log('🔥 フラッシュセールが開始!最後の1分!');
        }
      },
      onFinish: () => {
        // 🏁 フラッシュセール終了
        isActive.value = false;
        isEnded.value = true;
        console.log('🏁 フラッシュセールが終了!');
      }
    });
    
    // 🎯 ステータステキストの計算
    const statusText = computed(() => {
      if (isEnded.value) return '🏁 アクティビティは終了';
      if (isActive.value) return '🔥 セール中';
      return '⏰ 開始間近';
    });
    
    const timerLabel = computed(() => {
      if (isActive.value) return '🔥 セール終了カウントダウン';
      return '⏰ セール開始カウントダウン';
    });
    
    const buttonText = computed(() => {
      if (isEnded.value) return '😢 アクティビティは終了';
      if (isActive.value) return '🔥 即時購入';
      return '⏰ 開始待ち';
    });
    
    // 🛒 セールクリックの処理
    const handleSeckill = () => {
      if (isActive.value) {
        console.log('🛒 ユーザーがセールをクリック!');
        alert('🎉 おめでとう!セール成功!');
      }
    };
    
    // 🚀 自動的にカウントダウンを開始
    seckillCountDown.start();

    return {
      current: seckillCountDown.current,
      isActive,
      isEnded,
      statusText,
      timerLabel,
      buttonText,
      handleSeckill
    };
  },
};

📱 認証コードカウントダウン - 実用的なツール

SMS認証コード送信の典型的なシナリオ:

html
<template>
  <div class="verification-demo">
    <div class="phone-input">
      <label>📱 電話番号:</label>
      <input 
        v-model="phoneNumber" 
        type="tel" 
        placeholder="電話番号を入力してください"
        :disabled="isCodeSending"
      />
    </div>
    
    <div class="verification-section">
      <label>🔐 認証コード:</label>
      <input 
        v-model="verificationCode" 
        type="text" 
        placeholder="認証コードを入力してください"
        maxlength="6"
      />
      <button 
        class="send-code-btn"
        :class="{ disabled: !canSendCode }"
        :disabled="!canSendCode"
        @click="sendVerificationCode"
      >
        {{ codeButtonText }}
      </button>
    </div>
    
    <button class="verify-btn" @click="verifyCode">
      ✅ 検証
    </button>
  </div>
</template>
js
import { ref, computed } from 'vue';
import { useCountDown } from '@vant/use';

export default {
  setup() {
    const phoneNumber = ref('');
    const verificationCode = ref('');
    const isCodeSending = ref(false);
    const hasCodeSent = ref(false);
    
    // 📱 認証コードカウントダウン - 60秒
    const codeCountDown = useCountDown({
      time: 60 * 1000, // 60秒
      onChange: (current) => {
        console.log(`📱 認証コードカウントダウン:${current.seconds}秒`);
      },
      onFinish: () => {
        hasCodeSent.value = false;
        console.log('📱 認証コードカウントダウン終了、再送信可能');
      }
    });
    
    // 🎯 認証コードを送信できるかどうかの計算
    const canSendCode = computed(() => {
      return phoneNumber.value.length === 11 && !hasCodeSent.value && !isCodeSending.value;
    });
    
    // 🎨 ボタンテキスト
    const codeButtonText = computed(() => {
      if (isCodeSending.value) return '📤 送信中...';
      if (hasCodeSent.value) return `⏰ ${codeCountDown.current.value.seconds}秒後再送`;
      return '📱 認証コードを送信';
    });
    
    // 📤 認証コードを送信
    const sendVerificationCode = async () => {
      if (!canSendCode.value) return;
      
      isCodeSending.value = true;
      console.log(`📱 ${phoneNumber.value} に認証コードを送信...`);
      
      try {
        // 🌐 API呼び出しのシミュレーション
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        // ✅ 送信成功
        hasCodeSent.value = true;
        codeCountDown.start(); // カウントダウンを開始
        console.log('✅ 認証コード送信成功!');
        alert('📱 認証コードが送信されました、SMSを確認してください!');
        
      } catch (error) {
        console.error('❌ 認証コード送信失敗:', error);
        alert('❌ 認証コード送信失敗、再試行してください!');
      } finally {
        isCodeSending.value = false;
      }
    };
    
    // ✅ 認証コードを検証
    const verifyCode = () => {
      if (!verificationCode.value) {
        alert('🔐 認証コードを入力してください!');
        return;
      }
      
      if (verificationCode.value.length !== 6) {
        alert('🔐 認証コードは6桁の数字です!');
        return;
      }
      
      // 🎉 検証成功(実際の検証APIを呼び出す必要があります)
      console.log('✅ 認証コード検証成功!');
      alert('🎉 検証成功!');
      
      // 🔄 状態をリセット
      hasCodeSent.value = false;
      codeCountDown.reset();
      verificationCode.value = '';
    };

    return {
      phoneNumber,
      verificationCode,
      isCodeSending,
      canSendCode,
      codeButtonText,
      current: codeCountDown.current,
      sendVerificationCode,
      verifyCode
    };
  },
};

📚 API リファレンス

🔧 型定義

ts
// ⏰ 現在の時間情報
type CurrentTime = {
  days: number;         // 🗓️ 残り日数
  hours: number;        // 🕐 残り時間(0-23)
  total: number;        // ⏱️ 残り総時間(ミリ秒)
  minutes: number;      // ⏰ 残り分数(0-59)
  seconds: number;      // ⏱️ 残り秒数(0-59)
  milliseconds: number; // ⚡ 残りミリ秒(0-999)
};

// 🎮 カウントダウンコントローラ
type CountDown = {
  start: () => void;                    // ▶️ カウントダウンを開始
  pause: () => void;                    // ⏸️ カウントダウンを一時停止
  reset: (totalTime?: number) => void;  // 🔄 カウントダウンをリセット
  current: ComputedRef<CurrentTime>;    // 📊 現在の時間状態
};

// ⚙️ 設定オプション
type UseCountDownOptions = {
  time: number;                              // ⏰ カウントダウン時間(ミリ秒)
  millisecond?: boolean;                     // ⚡ ミリ秒単位のレンダリングを有効にするか
  onChange?: (current: CurrentTime) => void; // 🔔 時間変化コールバック
  onFinish?: () => void;                     // 🏁 カウントダウン終了コールバック
};

function useCountDown(options: UseCountDownOptions): CountDown;

📋 パラメータの説明

パラメータ説明デフォルト値
time⏰ カウントダウン時間
💡 単位:ミリ秒(1秒 = 1000ミリ秒)
number-
millisecond⚡ ミリ秒単位のレンダリングを有効にするか
💡 有効にすると更新頻度が高くなり、高精度なシナリオに適しています
booleanfalse
onChange🔔 カウントダウン変化コールバック
💡 時間が更新されるたびにトリガーされます
(current: CurrentTime) => void-
onFinish🏁 カウントダウン終了コールバック
💡 カウントダウンがゼロになるとトリガーされます
() => void-

🎮 戻り値の説明

パラメータ説明
current📊 現在の残り時間
💡 リアクティブデータ、自動的に更新されます
ComputedRef<CurrentTime>
start▶️ カウントダウンを開始
💡 現在の残り時間から計時を開始します
() => void
pause⏸️ カウントダウンを一時停止
💡 現在の時間を保持し、再開可能です
() => void
reset🔄 カウントダウンをリセット
💡 オプションで新しい時間を指定できます
(time?: number): void

⏰ CurrentTime の詳細

名前説明
total⏱️ 残り総時間(ミリ秒)
💡 全ての時間のミリ秒合計
number86400000
days🗓️ 残り日数
💡 完全な日数部分
number1
hours🕐 残り時間
💡 当日の残り時間(0-23)
number12
minutes⏰ 残り分数
💡 現在の時間の残り分数(0-59)
number30
seconds⏱️ 残り秒数
💡 現在の分の残り秒数(0-59)
number45
milliseconds⚡ 残りミリ秒
💡 現在の秒の残りミリ秒(0-999)
number500

🎯 実際のアプリケーションシナリオ

🛒 電子商取引シナリオ

js
// 🔥 フラッシュセールカウントダウン
const seckillTimer = useCountDown({
  time: getTimeUntilSeckill(), // セール開始までの時間
  onFinish: () => startSeckill() // セールを開始
});

// 🎁 限定キャンペーン
const promotionTimer = useCountDown({
  time: promotionEndTime - Date.now(),
  onChange: (current) => {
    if (current.total < 60000) { // 最後の1分
      showUrgentNotification();
    }
  }
});

📱 モバイルアプリケーション

js
// 📱 認証コードカウントダウン
const smsTimer = useCountDown({
  time: 60 * 1000, // 60秒
  onFinish: () => enableResendButton()
});

// 🎮 ゲームカウントダウン
const gameTimer = useCountDown({
  time: gameTimeLimit,
  millisecond: true, // ゲームは高精度を必要とする
  onFinish: () => endGame()
});

🏢 企業アプリケーション

js
// 📅 会議カウントダウン
const meetingTimer = useCountDown({
  time: timeUntilMeeting,
  onChange: (current) => {
    if (current.total <= 5 * 60 * 1000) { // 5分間のリマインダー
      sendMeetingReminder();
    }
  }
});

// ⏰ 労働時間カウントダウン
const workTimer = useCountDown({
  time: timeUntilWorkEnd,
  onFinish: () => showWorkEndNotification()
});

💡 ベストプラクティス

✅ 推奨されるアプローチ

  1. ⏰ 更新頻度の合理的な設定

    js
    // ✅ 一般的なシナリオでは秒単位の更新を使用
    const normalTimer = useCountDown({
      time: targetTime,
      millisecond: false // パフォーマンスを節約
    });
    
    // ✅ 高精度が必要なシナリオでのみミリ秒単位を使用
    const precisionTimer = useCountDown({
      time: targetTime,
      millisecond: true // 必要な場合のみ有効にする
    });
  2. 🔔 コールバック関数の活用

    js
    // ✅ 重要なタイミングでユーザーに通知
    const timer = useCountDown({
      time: targetTime,
      onChange: (current) => {
        if (current.total === 60000) { // 最後の1分
          showUrgentAlert();
        }
        if (current.total === 10000) { // 最後の10秒
          playCountdownSound();
        }
      },
      onFinish: () => {
        showCompletionMessage();
      }
    });
  3. 🎮 状態の正しい管理

    js
    // ✅ コンポーネントの状態と組み合わせる
    const isActive = ref(false);
    const timer = useCountDown({
      time: targetTime,
      onFinish: () => {
        isActive.value = false; // 状態を同期的に更新
      }
    });

❌ 避けるべきアプローチ

  1. 🚫 不要なミリ秒単位のレンダリング

    js
    // ❌ 通常のカウントダウンにはミリ秒単位の精度は不要
    const timer = useCountDown({
      time: 24 * 60 * 60 * 1000,
      millisecond: true // パフォーマンスを浪費
    });
  2. 🚫 終了状態の処理を忘れる

    js
    // ❌ カウントダウンの終了を処理していない
    const timer = useCountDown({
      time: targetTime
      // onFinish コールバックが不足している
    });
  3. 🚫 カウントダウンの頻繁なリセット

    js
    // ❌ レンダリングごとにリセットしないでください
    // 特定のイベントがトリガーされたときにリセットする必要があります

🛠️ デバッグのテクニック

🔍 時間フォーマットツール

js
// 🎨 時間表示の美化
const formatCountdown = (current) => {
  const { days, hours, minutes, seconds } = current;
  
  if (days > 0) {
    return `${days}日${hours}時間${minutes}分`;
  }
  if (hours > 0) {
    return `${hours}時間${minutes}分${seconds}秒`;
  }
  if (minutes > 0) {
    return `${minutes}分${seconds}秒`;
  }
  return `${seconds}秒`;
};

📊 カウントダウンのモニタリング

js
// 📈 カウントダウンの状態をモニタリング
const monitorCountdown = (current) => {
  console.log('⏰ カウントダウンの状態:', {
    総残り時間: `${current.total}ms`,
    フォーマット時間: formatCountdown(current),
    進捗パーセント: `${((initialTime - current.total) / initialTime * 100).toFixed(1)}%`
  });
};

📚 関連文書

⏰ 時間関連

🎮 状態管理

🛠️ 開発ツール

💡 実践ケース

Vant に基づく企業向けモバイルソリューション