50天50个小项目 (React19 + Tailwindcss V4) ✨ | AutoTextEffect(自动打字机)

📅 我们继续 50 个小项目挑战!------AutoTextEffect组件

仓库地址:https://gitee.com/hhm-hhm/50days50projects.git

​​

构建一个动态的打字机效果。这个效果不仅能够逐个字符地显示文本,还能模拟删除操作,为用户带来更丰富的视觉体验。

🌀 组件目标

  • 实现逐字显现文本的打字机效果
  • 支持调节打字速度
  • 模拟删除操作,增加互动性
  • 使用 TailwindCSS 快速构建 UI 样式

🔧 AutoTextEffect.tsx组件实现

TypeScript 复制代码
import React, { useState, useEffect, useRef } from 'react'

const AutoTextEffect: React.FC = () => {
    const fullText = '这是一个打字机效果示例,字母或汉字会一个个显示!'

    const [displayedText, setDisplayedText] = useState<string>('')
    const [speed, setSpeed] = useState<number>(150)
    const [isDeleting, setIsDeleting] = useState<boolean>(false)

    const indexRef = useRef<number>(0)
    const timeoutIdRef = useRef<NodeJS.Timeout | null>(null)

    const typeLoop = () => {
        const current = fullText.slice(0, indexRef.current)
        setDisplayedText(current)

        if (!isDeleting) {
            if (indexRef.current < fullText.length) {
                indexRef.current += 1
                timeoutIdRef.current = setTimeout(typeLoop, speed)
            } else {
                // 打字完成,停顿后开始删除
                setIsDeleting(true)
                timeoutIdRef.current = setTimeout(typeLoop, 1000)
            }
        } else {
            if (indexRef.current > 0) {
                indexRef.current -= 1
                timeoutIdRef.current = setTimeout(typeLoop, speed)
            } else {
                // 删除完成,停顿后重新打字
                setIsDeleting(false)
                timeoutIdRef.current = setTimeout(typeLoop, 500)
            }
        }
    }

    // 启动打字动画
    useEffect(() => {
        typeLoop()
        return () => {
            if (timeoutIdRef.current) {
                clearTimeout(timeoutIdRef.current)
            }
        }
    }, []) // 仅在挂载时启动

    // 监听 speed 变化:重置定时器以应用新速度
    useEffect(() => {
        if (timeoutIdRef.current) {
            clearTimeout(timeoutIdRef.current)
        }
        // 立即使用新速度继续动画
        timeoutIdRef.current = setTimeout(typeLoop, speed)
    }, [speed])

    return (
        <div className="flex min-h-screen flex-col items-center justify-center bg-gray-900 text-white">
            <div className="min-h-12 p-4 text-center text-2xl font-bold">
                {displayedText}
                <span className="inline-block w-[1ch] animate-ping align-bottom">|</span>
            </div>
            <div className="mt-8 flex flex-col items-center">
                <label htmlFor="speed" className="mb-2 text-lg">
                    调节打字速度
                </label>
                <input
                    id="speed"
                    type="range"
                    min="50"
                    max="500"
                    value={speed}
                    onChange={(e) => setSpeed(Number(e.target.value))}
                    className="w-64"
                />
                <span className="mt-2">速度: {speed}ms</span>
            </div>
            <div className="fixed right-20 bottom-5 z-100 text-2xl text-red-500">
                CSDN@Hao_Harrision
            </div>
        </div>
    )
}

export default AutoTextEffect

🔄 核心转换对照表

特性 Vue 3 React + TS
响应式变量 ref() useState(UI 相关) / useRef(非 UI)
生命周期 onMounted / onUnmounted useEffect(() => { ... }, [])
响应式监听 watch() useEffect(..., [deps])
表单绑定 v-model value + onChange
可变中间状态 模块级变量 useRef
清理副作用 onUnmounted useEffect 返回函数

1. 状态管理:

refuseState / useRef

Vue React
displayedText = ref('') useState<string>('')
speed = ref(150) useState<number>(150)
isDeleting = ref(false) useState<boolean>(false)
let index = 0(模块变量) useRef<number>(0)
let timeoutId = null `useRef<NodeJS.Timeout

为什么 indextimeoutIduseRef

因为它们是可变的中间状态 ,不需要触发 UI 更新。若用 useState,每次更新都会导致不必要的重渲染,且在闭包中可能捕获旧值。


2. 生命周期:

onMounted / onUnmounteduseEffect

  • onMounted(() => typeLoop())
    useEffect(() => { typeLoop(); return cleanup }, [])
  • onUnmounted(() => clearTimeout(...))
    → 在 useEffectcleanup 函数 中处理

✅ 这是 React 标准做法,确保组件卸载时清除定时器,防止内存泄漏。


3. 响应式监听:

watch(speed, ...)useEffect(..., [speed])

Vue 的 watch 监听 speed 变化并重置定时器:

TypeScript 复制代码
watch(speed, (newSpeed) => {
  clearTimeout(timeoutId)
  timeoutId = setTimeout(typeLoop, newSpeed)
})

React 中通过依赖数组实现:

TypeScript 复制代码
useEffect(() => {
  if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
  timeoutIdRef.current = setTimeout(typeLoop, speed);
}, [speed]); // 当 speed 变化时执行

⚠️ 注意:不能直接在 onChange 中调用 typeLoop(),因为 typeLoop 依赖当前 isDeletingindex 状态,必须由统一逻辑驱动。


4. 事件处理:

v-model → 受控组件

html 复制代码
<input v-model="speed" />

→ React 中手动绑定:

html 复制代码
<input
  value={speed}
  onChange={(e) => setSpeed(Number(e.target.value))}
/>

✅ 实现完全等效的双向绑定。


5. 动画与样式:

Tailwind 完全兼容

  • animate-ping 是 Tailwind 内置动画类(来自 @tailwindcss/aspect 或默认配置),无需额外处理;
  • 所有类名(min-h-[3rem], w-[1ch], bg-gray-900 等)直接复用;
  • 字体、颜色、布局行为一致。

6. 逻辑一致性保障

  • 打字 → 停顿 1s → 删除 → 停顿 0.5s → 循环;
  • 拖动滑块实时改变打字/删除速度;
  • 组件卸载时清除所有定时器;
  • 使用 useRef 确保 indextimeoutId 在闭包中始终是最新的。

7. TypeScript 类型安全

  • 明确定义 stringnumberboolean 类型;
  • timeoutIdRef 类型为 NodeJS.Timeout | null,避免运行时错误;
  • Number(e.target.value) 确保输入为数字。

🎯 最终效果

  • 页面中央显示打字机动画,带闪烁光标(| + animate-ping);
  • 下方可拖动滑块调节打字/删除速度(50ms ~ 500ms);
  • 支持中文字符逐字显示;
  • 组件卸载安全,无内存泄漏;
  • 完全类型安全,符合 React 最佳实践。

🎨 TailwindCSS 样式重点讲解

类名 作用
flex, min-h-screen, flex-col, items-center, justify-center 使用 Flexbox 创建一个全屏垂直居中的布局
bg-gray-900 设置背景颜色为深灰色,增加对比度和视觉层次感
text-white 设置文字颜色为白色,确保在深色背景下清晰可见
min-h-[3rem], p-4, text-center, text-2xl, font-bold 控制文本显示区域的高度、内边距、文本对齐方式、字体大小及加粗效果
inline-block, w-[1ch], animate-ping, align-bottom 实现光标的样式设置,包括宽度(字符单位)、动画效果以及垂直对齐
mt-8, flex, flex-col, items-center 调整输入范围控件和其他元素的外边距、使用Flexbox进行布局,并使其内容居中对齐
mb-2, text-lg 为标签提供下边距和字体大小
w-64 设置滑动条的最大宽度
mt-2 给速度显示文本添加上边距
[🎯 TailwindCSS 样式说明]

🦌 路由组件 + 常量定义

router/index.tsx children数组中添加子路由

TypeScript 复制代码
{
    path: '/',
    element: <App />,
    children: [
       ...
      {
                path: '/AutoTextEffect',
                lazy: () =>
                    import('@/projects/AutoTextEffect').then((mod) => ({
                        Component: mod.default,
                    })),
       },
    ],
 },

constants/index.tsx 添加组件预览常量``

TypeScript 复制代码
import demo30Img from '@/assets/pic-demo/demo-30.png'
省略部分....
export const projectList: ProjectItem[] = [
    省略部分....
     {
        id: 30,
        title: 'Auto Text Effect',
        image: demo30Img,
        link: 'AutoTextEffect',
    },
]

🚀 小结

创建一个具有交互性的打字机效果。

扩展基础的打字机效果:

  • ✅ 提供一个文本框让用户自行输入想要展示的文字。
  • ✅ 字符颜色渐变
  • ✅ 背景闪烁
  • ✅ 添加粒子爆炸或其他动画效果

📅 明日预告: 我们将完成PasswordGenerator组件,一个密码生成器组件,可以对生成的密码进行配置、复制。🚀

感谢阅读,欢迎点赞、收藏和分享 😊

原文链接:https://blog.csdn.net/qq_44808710/article/details/149401154

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

相关推荐
IT_陈寒2 小时前
Vite 3.0 实战:5个优化技巧让你的开发效率提升50%
前端·人工智能·后端
Sheldon一蓑烟雨任平生2 小时前
Vue3 低代码平台项目实战(下)
低代码·typescript·vue3·低代码平台·json schema·vue3项目
玲小珑2 小时前
React 防抖函数中的闭包陷阱与解决方案
前端·react.js
咖啡の猫2 小时前
TypeScript编译选项
前端·javascript·typescript
想学后端的前端工程师2 小时前
【Vue3响应式原理深度解析:从Proxy到依赖收集】
前端·javascript·vue.js
小徐不会敲代码~3 小时前
Vue3 学习 5
前端·学习·vue
_Kayo_3 小时前
vue3 状态管理器 pinia 用法笔记1
前端·javascript·vue.js
How_doyou_do3 小时前
工程级前端智能体FrontAgent
前端
2501_944446003 小时前
Flutter&OpenHarmony日期时间选择器实现
前端·javascript·flutter