50天50个小项目 (React19 + Tailwindcss V4) ✨| AnimatedCountdown(倒计时组件)
使用React19 、TailwindCSS构建的倒计时动画组件。该组件通过Composition API管理状态,实现从"GO"字样到数字3、2、1的动画过渡效果,包含缩放、旋转和透明度变化等视觉效果。
📅 元旦休息三天,今天我们继续 50 个小项目挑战!——AnimatedCountdown组件
仓库地址:https://gitee.com/hhm-hhm/50days50projects.git
构建一个引人注目的倒计时动画组件。该组件首先展示“GO”字样,随后通过一系列数字(3, 2, 1)的动画过渡,最终再次回到初始状态。这个过程不仅视觉效果出色,而且非常适合用于启动页或游戏开始前的准备阶段。
🌀 组件目标
- 显示“GO”字样作为起始点
- 使用数字动画进行倒计时(3, 2, 1)
- 动画过程中包括缩放、旋转和透明度变化
- 提供一个按钮来启动整个倒计时流程
- 使用
TailwindCSS快速构建 UI 样式
🔧 AnimatedCountdown.tsx组件实现
import React, { useState } from 'react'
const AnimatedCountdown: React.FC = () => {
const numbers = [3, 2, 1] as const // readonly [3, 2, 1]
const [currentIndex, setCurrentIndex] = useState<number>(0)
const [stage, setStage] = useState<'in' | 'center' | 'out' | ''>('in')
const [started, setStarted] = useState<boolean>(false)
const [showGo, setShowGo] = useState<boolean>(true)
const wait = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
const reset = () => {
setStarted(false)
setShowGo(true)
setCurrentIndex(0)
setStage('in')
}
const runAnimation = async () => {
setShowGo(false)
for (let i = 0; i < numbers.length; i++) {
setCurrentIndex(i)
// in
setStage('in')
await wait(100)
// center
setStage('center')
await wait(1000)
// out
setStage('out')
await wait(500)
}
// Show "GO"
setShowGo(true)
setStage('')
await wait(1000)
reset()
}
const startCountdown = () => {
setStarted(true)
runAnimation()
}
return (
<div className="fixed inset-0 flex items-center justify-center bg-gray-900">
{/* GO 文字 */}
{showGo && <div className="text-7xl font-bold text-blue-500">GO</div>}
{/* 倒计时数字:只渲染当前数字 */}
{!showGo && (
<div className="relative h-[200px] w-[200px]">
<span
className={`absolute top-1/2 left-1/2 origin-[50%_200%] text-6xl font-bold text-blue-600 transition-all duration-500 ease-in-out ${
stage === 'in'
? 'scale-0 rotate-[120deg] opacity-0'
: stage === 'center'
? 'scale-100 rotate-0 opacity-100'
: stage === 'out'
? 'scale-0 rotate-[-120deg] opacity-0'
: ''
}`}
style={{ transform: 'translate(-50%, -50%)' }}>
{numbers[currentIndex]}
</span>
</div>
)}
{/* 开始按钮 */}
{!started && (
<div className="absolute bottom-20">
<button
onClick={startCountdown}
className="cursor-pointer rounded bg-blue-500 px-6 py-2 text-white shadow transition-colors hover:bg-blue-600"
aria-label="开始倒计时">
开始
</button>
</div>
)}
<div className="fixed right-20 bottom-5 z-100 text-2xl text-red-500">
CSDN@Hao_Harrision
</div>
</div>
)
}
export default AnimatedCountdown
🔄 核心转换对照表
| 功能 | Vue 3 | React + TS |
|---|---|---|
| 响应式状态 | ref() |
useState() |
| 条件渲染 | v-if / v-else |
{bool && element} |
| 列表渲染 | v-for |
.map() |
| 类名绑定 | :class |
模板字符串 |
| 异步流程 | async/await |
相同 |
| 居中定位 | 自动? | 需手动 transform |
🔁 转换说明
1. 状态管理:ref → useState
| Vue | React |
|---|---|
const currentIndex = ref(0) |
const [currentIndex, setCurrentIndex] = useState(0) |
stage, started, showGo 同理 |
全部使用 useState,并明确类型 |
✅ 使用联合类型 'in' | 'center' | 'out' | '' 提升类型安全。
2. 条件渲染:v-if / v-else → {condition && (...)}
- Vue:
<div v-if="showGo">GO</div> <div v-else>...</div> - React:
{showGo && <div>GO</div>} {!showGo && <div>...</div>}
✅ 逻辑等价,符合 React JSX 规范。
3. 列表渲染:v-for → .map()
- Vue:
<span v-for="(num, idx) in numbers" :key="idx" v-show="currentIndex === idx"> - React:
{numbers.map((num, idx) => ( <span key={idx} ... >{num}</span> ))}
⚠️ 关键差异:
Vue 用 v-show 控制显示/隐藏(元素始终存在),而 React 中我们渲染所有 span,但通过 CSS 动画控制视觉状态。
但由于 stage 是全局状态(同一时间只有一种动画阶段),且数字位置重叠,即使三个 span 同时存在,视觉上也只会看到当前数字,效果一致。
✅ 若想严格模拟
v-show,可加idx === currentIndex ? (...) : null,但非必要。
4. 动画控制:CSS 类名动态绑定
Vue 使用 :class="[...]" 数组语法,React 使用模板字符串:
className={`... ${stage === 'in' ? 'scale-0 ...' : ...}`}
✅ 完全等效,Tailwind 类名直接复用。
5. 异步动画:async/await 保留
wait(ms)函数保持不变;runAnimation()仍为async函数;- 使用
setTimeout实现延时,与 Vue 行为一致。
✅ React 中 useState 更新是异步的,但在此场景下(顺序 await)完全兼容。
6. 居中定位修复
Vue 中 top-1/2 left-1/2 通常配合 transform: translate(-50%, -50%) 居中。
Tailwind 的 top-1/2 left-1/2 不会自动加 transform,因此需手动添加:
style={{ transform: 'translate(-50%, -50%)' }}
✅ 确保数字精确居中。
7. 无障碍与体验优化
- 添加
aria-label到按钮; - 补充
transition-colors使 hover 更平滑。
✅ 动画行为验证
| 阶段 | 行为 | React 实现 |
|---|---|---|
| 初始 | 显示 “GO” + “开始”按钮 | ✅ |
| 点击开始 | 隐藏 GO,开始 3→2→1 动画 | ✅ |
| 每个数字 | in(缩小旋转淡出)→ center(放大居中)→ out(缩小反向旋转淡出) | ✅ |
| 结束 | 显示 “GO” 1 秒后重置 | ✅ |
💡 使用建议
- 此组件适合用作 游戏/测验前的倒计时引导;
- 可通过 props 扩展(如自定义数字、颜色、回调等);
- 动画流畅,无额外依赖。
🎨 TailwindCSS 样式重点讲解
| 类名 | 作用 |
|---|---|
fixed, inset-0, flex, items-center, justify-center, bg-white |
创建全屏居中的白色背景容器 |
text-7xl, font-bold, text-blue-500 |
设置“GO”文字的大小、粗细和颜色 |
relative, h-[200px], w-[200px] |
定义倒计时数字容器的尺寸 |
absolute, top-1/2, left-1/2, origin-[50%_200%] |
将每个数字定位在容器中心并设置旋转原点 |
transition-all, duration-500, ease-in-out |
添加平滑的动画过渡效果 |
scale-0, rotate-[120deg], opacity-0 |
“进入”阶段的动画属性 |
scale-100, rotate-0, opacity-100 |
“中间”阶段的动画属性 |
scale-0, rotate-[-120deg], opacity-0 |
“退出”阶段的动画属性 |
absolute, bottom-20 |
开始按钮的位置 |
rounded, bg-blue-500, px-6, py-2, shadow, hover:bg-blue-600 |
开始按钮的样式和悬停效果 |
🦌 路由组件 + 常量定义
router/index.tsx 中 children数组中添加子路由
{
path: '/',
element: <App />,
children: [
...
{
path: '/AnimatedCountdown',
lazy: () =>
import('@/projects/AnimatedCountdown').then((mod) => ({
Component: mod.default,
})),
},
],
},
constants/index.tsx 添加组件预览常量
import demo34Img from '@/assets/pic-demo/demo-34.png'
省略部分....
export const projectList: ProjectItem[] = [
省略部分....
{
id: 34,
title: 'Animated Countdown',
image: demo34Img,
link: 'AnimatedCountdown',
},
]
🚀 小结
通过这篇文章,我们使用 React19 和 TailwindCSS 创建一个包含丰富动画效果的倒计时组件。无论是用于网页引导页还是游戏启动界面,这样的组件都能显著提升用户体验。
你可以考虑以下方式来扩展这个基础的倒计时组件:
✅ 增加音效支持:在每个数字出现时播放声音。
✅ 自定义倒计时时间:允许用户输入或选择倒计时的具体秒数。
✅ 添加结束动作:例如跳转到新页面或触发其他事件。
✅ 多语言支持:提供不同语言的“GO”文本或其他提示信息。
📅 明日预告: 我们将完成ImageCarousel组件,实现一个图片轮播组件。🚀
感谢阅读,欢迎点赞、收藏和分享 😊
原文链接:https://blog.csdn.net/qq_44808710/article/details/149517053
每天造一个轮子,码力暴涨不是梦!🚀
更多推荐


所有评论(0)