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

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

相关推荐
华玥作者21 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_1 天前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠1 天前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang201509281 天前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC1 天前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务1 天前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整1 天前
面试点(网络层面)
前端·网络
VT.馒头1 天前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
phltxy1 天前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron07071 天前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js