⏰ 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 | ⚡ ミリ秒単位のレンダリングを有効にするか 💡 有効にすると更新頻度が高くなり、高精度なシナリオに適しています | boolean | false |
| onChange | 🔔 カウントダウン変化コールバック 💡 時間が更新されるたびにトリガーされます | (current: CurrentTime) => void | - |
| onFinish | 🏁 カウントダウン終了コールバック 💡 カウントダウンがゼロになるとトリガーされます | () => void | - |
🎮 戻り値の説明
| パラメータ | 説明 | 型 |
|---|---|---|
| current | 📊 現在の残り時間 💡 リアクティブデータ、自動的に更新されます | ComputedRef<CurrentTime> |
| start | ▶️ カウントダウンを開始 💡 現在の残り時間から計時を開始します | () => void |
| pause | ⏸️ カウントダウンを一時停止 💡 現在の時間を保持し、再開可能です | () => void |
| reset | 🔄 カウントダウンをリセット 💡 オプションで新しい時間を指定できます | (time?: number): void |
⏰ CurrentTime の詳細
| 名前 | 説明 | 型 | 例 |
|---|---|---|---|
| total | ⏱️ 残り総時間(ミリ秒) 💡 全ての時間のミリ秒合計 | number | 86400000 |
| days | 🗓️ 残り日数 💡 完全な日数部分 | number | 1 |
| hours | 🕐 残り時間 💡 当日の残り時間(0-23) | number | 12 |
| minutes | ⏰ 残り分数 💡 現在の時間の残り分数(0-59) | number | 30 |
| seconds | ⏱️ 残り秒数 💡 現在の分の残り秒数(0-59) | number | 45 |
| milliseconds | ⚡ 残りミリ秒 💡 現在の秒の残りミリ秒(0-999) | number | 500 |
🎯 実際のアプリケーションシナリオ
🛒 電子商取引シナリオ
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()
});💡 ベストプラクティス
✅ 推奨されるアプローチ
⏰ 更新頻度の合理的な設定
js// ✅ 一般的なシナリオでは秒単位の更新を使用 const normalTimer = useCountDown({ time: targetTime, millisecond: false // パフォーマンスを節約 }); // ✅ 高精度が必要なシナリオでのみミリ秒単位を使用 const precisionTimer = useCountDown({ time: targetTime, millisecond: true // 必要な場合のみ有効にする });🔔 コールバック関数の活用
js// ✅ 重要なタイミングでユーザーに通知 const timer = useCountDown({ time: targetTime, onChange: (current) => { if (current.total === 60000) { // 最後の1分 showUrgentAlert(); } if (current.total === 10000) { // 最後の10秒 playCountdownSound(); } }, onFinish: () => { showCompletionMessage(); } });🎮 状態の正しい管理
js// ✅ コンポーネントの状態と組み合わせる const isActive = ref(false); const timer = useCountDown({ time: targetTime, onFinish: () => { isActive.value = false; // 状態を同期的に更新 } });
❌ 避けるべきアプローチ
🚫 不要なミリ秒単位のレンダリング
js// ❌ 通常のカウントダウンにはミリ秒単位の精度は不要 const timer = useCountDown({ time: 24 * 60 * 60 * 1000, millisecond: true // パフォーマンスを浪費 });🚫 終了状態の処理を忘れる
js// ❌ カウントダウンの終了を処理していない const timer = useCountDown({ time: targetTime // onFinish コールバックが不足している });🚫 カウントダウンの頻繁なリセット
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)}%`
});
};📚 関連文書
⏰ 時間関連
- useEventListener - イベントリスナー管理
- usePageVisibility - ページの可視性検出
- useRaf - アニメーションフレーム管理
🎮 状態管理
- useToggle - ブール値の状態切り替え
- useClickAway - 外部クリックのリスニング
- useWindowSize - ウィンドウサイズのリスニング
🛠️ 開発ツール
- コンポジションAPIの紹介 - より多くの便利なHookについて
- イベント処理のベストプラクティス - イベント処理のテクニック
- パフォーマンス最適化ガイド - パフォーマンス最適化の方法
💡 実践ケース
- Button ボタン - ボタンコンポーネントのアプリケーション
- Toast 軽量通知 - メッセージ通知コンポーネント
- Dialog ダイアログ - ダイアログコンポーネントのアプリケーション
- Popup ポップアップレイヤー - ポップアップレイヤーコンポーネント