打字机

一句句文字在闪烁的光标后逐字打出,停顿后删除,再继续下一句。

  • JS
  • 文字
  • 循环
  • 收录于

AI 提示词

用 React 制作一个打字机效果,循环显示“把想法做出来。”“把细节打磨好。”“复制提示词。”三句话。每 55ms 打出一个字,完整显示后停留 1.4 秒,再每 30ms 删除一个字,然后开始下一句。用一条 setTimeout 链驱动,并在组件卸载时清除。文字后面显示一个 2px 宽的光标,用 CSS steps(1) 动画每 1 秒闪烁一次,打字过程中停止闪烁。用 Array.from(text) 遍历字符,避免拆坏表情符号和汉字。把当前完整句子放在 aria-label 中,并对屏幕阅读器隐藏动画文字。在 prefers-reduced-motion 下直接显示整句,每 2.5 秒切换一次。

源代码

Demo.tsx
'use client'

import { useEffect, useState } from 'react'
import s from './demo.module.css'

export default function Typewriter({ labels }: { labels: Record<string, string> }) {
  const [text, setText] = useState('')
  const [typing, setTyping] = useState(false)
  const [full, setFull] = useState(labels.phrases.split('|')[0])

  useEffect(() => {
    const phrases = labels.phrases.split('|')
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
    let timer: ReturnType<typeof setTimeout>
    let p = 0
    let i = 0
    let deleting = false

    const tick = () => {
      const chars = Array.from(phrases[p])
      setFull(phrases[p])
      if (reduced) {
        setText(phrases[p])
        p = (p + 1) % phrases.length
        timer = setTimeout(tick, 2500)
        return
      }
      if (!deleting && i < chars.length) {
        i++
        setTyping(true)
        timer = setTimeout(tick, 55)
      } else if (!deleting) {
        deleting = true
        setTyping(false)
        timer = setTimeout(tick, 1400)
        return
      } else if (i > 0) {
        i--
        setTyping(true)
        timer = setTimeout(tick, 30)
      } else {
        deleting = false
        p = (p + 1) % phrases.length
        timer = setTimeout(tick, 250)
        return
      }
      setText(chars.slice(0, i).join(''))
    }
    tick()
    return () => clearTimeout(timer)
  }, [labels.phrases])

  return (
    <p className={s.line} aria-label={full}>
      <span aria-hidden="true">{text}</span>
      <span className={typing ? s.caret : `${s.caret} ${s.blink}`} aria-hidden="true" />
    </p>
  )
}
demo.module.css
.line {
  display: flex;
  align-items: center;
  min-height: 1.4em;
  margin: 0;
  font: 600 var(--text-2xl) / 1.2 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--stage-ink);
  white-space: nowrap;
}

.caret {
  width: 2px;
  height: 1.05em;
  margin-left: 3px;
  background: var(--stage-accent);
}

.blink { animation: blink 1s steps(1) infinite; }

@keyframes blink { 50% { opacity: 0; } }

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。