📅 元旦休息三天,今天我们继续 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)
stagestartedshowGo 同理 全部使用 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 样式重点讲解

🎯 TailwindCSS 样式说明
类名 作用
fixedinset-0flexitems-centerjustify-centerbg-white 创建全屏居中的白色背景容器
text-7xlfont-boldtext-blue-500 设置“GO”文字的大小、粗细和颜色
relativeh-[200px]w-[200px] 定义倒计时数字容器的尺寸
absolutetop-1/2left-1/2origin-[50%_200%] 将每个数字定位在容器中心并设置旋转原点
transition-allduration-500ease-in-out 添加平滑的动画过渡效果
scale-0rotate-[120deg]opacity-0 “进入”阶段的动画属性
scale-100rotate-0opacity-100 “中间”阶段的动画属性
scale-0rotate-[-120deg]opacity-0 “退出”阶段的动画属性
absolutebottom-20 开始按钮的位置
roundedbg-blue-500px-6py-2shadowhover: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

每天造一个轮子,码力暴涨不是梦!🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐