Vant 4 CountDown 倒计时组件完全指南:从毫秒级渲染到实例方法控制
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
CountDown 是 Vant 4 移动端组件库中用于实时展示倒计时数值的组件,支持毫秒级精度与自定义时间格式。本文将以 CountDown 官方文档 为主线,结合组件源码、@vant/use中useCountDown的底层实现与测试用例,系统讲解倒计时的引入、五种典型用法、完整 API 与主题定制方案,帮助你在秒杀、活动开售、验证码重发、答题限时等移动端场景中快速落地可靠的倒计时能力。
组件能力概览与引入方式
CountDown 组件定位轻量:核心源码仅由一个 CountDown.tsx 渲染组件与一个 utils.ts 格式化工具组成,全部时间计算逻辑下沉到独立的组合式函数useCountDown,组件层保持高度精简。
通过以下方式即可全局注册组件,更多注册方式(如局部注册、按需引入)可参考 组件注册指南:
import { createApp } from 'vue'; import { CountDown } from 'vant'; const app = createApp(); app.use(CountDown);注册后即可在模板中使用<van-count-down>标签。同时,index.ts 中声明了VanCountDown的全局组件类型,配合 Volar 可在模板中获得完整的类型提示与属性校验。
基础用法:以time为核心
time属性表示倒计时总时长,单位为毫秒。下面的示例创建了一个 30 小时的倒计时:
<van-count-down :time="time" />import { ref } from 'vue'; export default { setup() { const time = ref(30 * 60 * 60 * 1000); return { time }; }, };组件在挂载时即通过watch(() => props.time, resetTime, { immediate: true })监听time的变化,CountDown.tsx 中一旦time属性变化就会重置剩余时间;若auto-start为true则重置后自动开始。因此动态修改time即可实现"重置为新的时长"的常见需求。
需要说明的是,组件根节点渲染了role="timer"无障碍属性,方便屏幕阅读器识别倒计时区域。
自定义格式:format 的解析规则
通过format属性可以完全控制倒计时文本的内容,例如:
<van-count-down :time="time" format="DD 天 HH 时 mm 分 ss 秒" />format支持以下占位符,各占位符都会被零填充(如05):
| 格式 | 说明 |
|---|---|
| DD | 天数 |
| HH | 小时 |
| mm | 分钟 |
| ss | 秒数 |
| S | 毫秒(1 位) |
| SS | 毫秒(2 位) |
| SSS | 毫秒(3 位) |
默认值为HH:mm:ss。这里有两个值得留意的行为:
- 毫秒占位符按从长到短的顺序匹配:先匹配
SSS,再匹配SS,最后匹配单个S,取毫秒值的前 1 或前 2 位展示; - 当 format 中省略了某个高位单位时,该单位会被"折算"进下一个单位。这一逻辑实现在 utils.ts 的
parseFormat中:
if (format.includes('DD')) { format = format.replace('DD', padZero(days)); } else { hours += days * 24; // 未使用 DD 时,天数折算进小时 } // HH / mm / ss 同理逐级折算也就是说,若 format 不含DD,小时会包含"天数 × 24";若不含HH,分钟会折算 "小时 × 60";若不含mm,秒数会折算 "分钟 × 60";若不含ss,则毫秒会包含 "秒 × 1000"。这一规则保证了任何省略写法下总时长都不会丢失,测试用例should format incomplete time correctly(test/index.spec.tsx)专门验证了省略天数的折算行为。
毫秒级渲染:microTick 与 macroTick 的分工
倒计时默认每秒渲染一次;开启millisecond属性后即切换为毫秒级渲染:
<van-count-down millisecond :time="time" format="HH:mm:ss:SS" />两种渲染模式由useCountDown内部的tick决定(packages/vant-use/src/useCountDown/index.ts):
- 毫秒模式:调用
microTick,每个requestAnimationFrame回调中都重新计算剩余时间并更新视图,保证毫秒位实时跳动; - 秒级模式:调用
macroTick,利用isSameSecond判断秒数是否变化,同一秒内不触发重复渲染,大幅降低无效渲染开销。
同时,tick中通过inBrowser判断在服务端(SSR)环境下不启动计时,避免水合不一致的问题。两种模式均以endTime = Date.now() + remain.value为基准计算Math.max(endTime - Date.now(), 0)得出剩余时间,因此即便requestAnimationFrame被节流,最终时间也不会漂移,比逐帧递减remain更准确。
自定义样式:插槽与 timeData
通过默认插槽可以完全自定义倒计时的外观,插槽参数timeData即CurrentTime对象,字段含义见下文 API 表格:
<van-count-down :time="time"> <template #default="timeData"> <span class="block">{{ timeData.hours }}</span> <span class="colon">:</span> <span class="block">{{ timeData.minutes }}</span> <span class="colon">:</span> <span class="block">{{ timeData.seconds }}</span> </template> </van-count-down> <style> .colon { display: inline-block; margin: 0 4px; color: #1989fa; } .block { display: inline-block; width: 22px; color: #fff; font-size: 12px; text-align: center; background-color: #1989fa; } </style>从 CountDown.tsx 可以看到,渲染逻辑为:存在默认插槽时优先渲染插槽内容(传入current.value),否则渲染parseFormat格式化后的纯文本。因此插槽方案与 format 方案二选一,插槽方案适合将数字渲染为色块、胶囊、图片等富样式。仓库中的官方示例 demo/index.vue 还在此基础上加入了圆角与主题色变量,可作为参考。
手动控制:start / pause / reset
通过 ref 获取组件实例后,可以调用start、pause、reset三个实例方法,实现按钮驱动的倒计时:
<van-count-down ref="countDown" millisecond :time="3000" :auto-start="false" format="ss:SSS" @finish="onFinish" /> <van-grid clickable> <van-grid-item text="开始" icon="play-circle-o" @click="start" /> <van-grid-item text="暂停" icon="pause-circle-o" @click="pause" /> <van-grid-item text="重置" icon="replay" @click="reset" /> </van-grid>import { showToast } from 'vant'; export default { setup() { const countDown = ref(null); const start = () => { countDown.value.start(); }; const pause = () => { countDown.value.pause(); }; const reset = () => { countDown.value.reset(); }; const onFinish = () => showToast('倒计时结束'); return { start, pause, reset, onFinish, countDown, }; }, };三个方法的行为与底层实现对应如下(详见 packages/vant-use/src/useCountDown/index.ts):
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| start | 开始倒计时;以当前剩余时间重新锚定endTime,已结束时调用则从 0 继续 | - | - |
| pause | 暂停倒计时,取消当前rafId并保持剩余时间 | - | - |
| reset | 重设倒计时为time属性值;若auto-start为true,重设后会自动开始 | - | - |
组件通过useExpose(packages/vant/src/composables/use-expose.ts)将这三个方法挂载到组件实例的 proxy 上,因此模板中ref="countDown"拿到的实例可以直接调用。测试用例对上述三种操作均做了覆盖:should start counting after calling the start method、should pause counting after calling the pause method、should reset time after calling the reset method(见 test/index.spec.tsx)。
另外,useCountDown还通过onActivated/onDeactivated钩子与<KeepAlive>协同:组件被缓存停用时自动暂停、重新激活时自动续跑,测试用例should pause counting when deactivated验证了该行为,在列表页与详情页间切换的场景下可避免倒计时被错误推进。
API 参考
Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| time | 倒计时时长,单位毫秒 | number | string | 0 |
| format | 时间格式 | string | HH:mm:ss |
| auto-start | 是否自动开始倒计时 | boolean | true |
| millisecond | 是否开启毫秒级渲染 | boolean | false |
time在 CountDown.tsx 中通过makeNumericProp(0)声明,因此字符串类型的数字也会被正确转换为number后传入useCountDown。
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| finish | 倒计时结束时触发 | - |
| change | 倒计时变化时触发 | currentTime: CurrentTime |
finish与change分别由useCountDown的onFinish、onChange回调转发。setRemain中当剩余时间为 0 时会自动pause并触发finish;change则在每次剩余时间更新时触发,回调参数为完整的CurrentTime对象,测试用例should emit change event when counting验证了其参数结构。
Slots
| 名称 | 说明 | 参数 |
|---|---|---|
| default | 自定义内容 | currentTime: CurrentTime |
CurrentTime 格式
| 名称 | 说明 | 类型 |
|---|---|---|
| total | 剩余总时间(单位毫秒) | number |
| days | 剩余天数 | number |
| hours | 剩余小时 | number |
| minutes | 剩余分钟 | number |
| seconds | 剩余秒数 | number |
| milliseconds | 剩余毫秒 | number |
该对象由 packages/vant-use/src/useCountDown/index.ts 的parseTime基于DAY / HOUR / MINUTE / SECOND常量逐级取整生成,total始终等于原始剩余毫秒数。
类型定义
组件导出以下类型定义,便于在 TypeScript 项目中获得强类型支持:
import type { CountDownProps, CountDownInstance, CountDownCurrentTime, } from 'vant';CountDownInstance是组件实例的类型,用法如下:
import { ref } from 'vue'; import type { CountDownInstance } from 'vant'; const countDownRef = ref<CountDownInstance>(); countDownRef.value?.start();从 types.ts 可以看到,CountDownInstance基于ComponentPublicInstance<CountDownProps, CountDownExpose>构造,其中CountDownExpose定义了start、pause、reset三个实例方法的签名;CountDownCurrentTime则直接复用@vant/use的CurrentTime类型。TS 环境下建议将 demo 中的ref(null)写法替换为ref<CountDownInstance>()以获得方法调用时的完整提示。
主题定制
组件通过 index.less 提供了三个 CSS 变量,可在根节点或通过 ConfigProvider 组件 统一覆盖,实现与业务主题色的联动:
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-count-down-text-color | var(--van-text-color) | 文本颜色 |
| --van-count-down-font-size | var(--van-font-size-md) | 字号 |
| --van-count-down-line-height | var(--van-line-height-md) | 行高 |
这三个变量默认值均引用 Vant 全局基础变量,因此在不做任何配置时倒计时文本会自动跟随主题风格;自定义时只需在组件外层覆盖同名变量即可,例如配合插槽方案渲染的彩色数字块时,可同步调整字号与行高保证数字块间距统一。
常见问题
在 iOS 系统上倒计时不生效?
如果你遇到了在 iOS 上倒计时不生效的问题,请确认在创建 Date 对象时没有使用new Date('2020-01-01')这样的写法。iOS 的 JavaScript 引擎不支持以中划线(-)分隔的日期字符串格式,解析会得到Invalid Date,进而导致所有时间计算返回NaN;正确写法是使用斜杠分隔的new Date('2020/01/01'),或将日期拆分为参数形式new Date(2020, 0, 1)。由于 CountDown 内部以Date.now()作为计时基准,若业务侧传入的结束时间先经过错误的 Date 解析,就会表现为"倒计时不生效",排查时可优先检查结束时间的构造方式。
若需要基于某个"未来时间点"而非时长运行倒计时,可先计算endTime.getTime() - Date.now()得到毫秒差,再作为time传入组件,同时注意遵循上述 iOS 兼容的日期构造写法。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考