mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-07 03:07:56 +08:00
feat(web): 落地页“启动输出”加入打字机效果(Typewriter)
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
Cpu,
|
||||
} from 'lucide-react'
|
||||
import { CryptoFeatureCard } from './CryptoFeatureCard'
|
||||
import Typewriter from './Typewriter'
|
||||
|
||||
// Animation variants
|
||||
const fadeInUp = {
|
||||
@@ -515,14 +516,19 @@ export function LandingPage() {
|
||||
boxShadow: '0 20px 60px rgba(240, 185, 11, 0.2)',
|
||||
}}
|
||||
>
|
||||
<pre className='text-sm font-mono' style={{ color: '#0ECB81' }}>
|
||||
{`$ git clone https://github.com/...
|
||||
$ docker compose up -d
|
||||
🚀 NOFX 已启动
|
||||
✓ AI 代理运行中
|
||||
✓ 交易所已连接
|
||||
✓ 策略已激活`}
|
||||
</pre>
|
||||
<Typewriter
|
||||
lines={[
|
||||
'$ git clone https://github.com/tinkle-community/nofx',
|
||||
'$ docker compose up -d',
|
||||
'🚀 NOFX 已启动',
|
||||
'✓ AI 代理运行中',
|
||||
'✓ 交易所已连接',
|
||||
'✓ 策略已激活',
|
||||
]}
|
||||
typingSpeed={24}
|
||||
lineDelay={350}
|
||||
className='text-sm font-mono'
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface TypewriterProps {
|
||||
lines: string[]
|
||||
typingSpeed?: number // 毫秒/字符
|
||||
lineDelay?: number // 每行结束的额外等待
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Typewriter({
|
||||
lines,
|
||||
typingSpeed = 25,
|
||||
lineDelay = 400,
|
||||
className,
|
||||
}: TypewriterProps) {
|
||||
const [text, setText] = useState('')
|
||||
const [showCursor, setShowCursor] = useState(true)
|
||||
const lineIndexRef = useRef(0)
|
||||
const charIndexRef = useRef(0)
|
||||
const timerRef = useRef<number | null>(null)
|
||||
const blinkRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
function typeNext() {
|
||||
const currentLine = lines[lineIndexRef.current] ?? ''
|
||||
if (charIndexRef.current < currentLine.length) {
|
||||
setText((t) => t + currentLine[charIndexRef.current])
|
||||
charIndexRef.current += 1
|
||||
timerRef.current = window.setTimeout(typeNext, typingSpeed)
|
||||
} else {
|
||||
// 行结束
|
||||
if (lineIndexRef.current < lines.length - 1) {
|
||||
setText((t) => t + '\n')
|
||||
lineIndexRef.current += 1
|
||||
charIndexRef.current = 0
|
||||
timerRef.current = window.setTimeout(typeNext, lineDelay)
|
||||
} else {
|
||||
// 最后一行输入完毕
|
||||
timerRef.current = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeNext()
|
||||
|
||||
// 光标闪烁
|
||||
blinkRef.current = window.setInterval(() => {
|
||||
setShowCursor((v) => !v)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) window.clearTimeout(timerRef.current)
|
||||
if (blinkRef.current) window.clearInterval(blinkRef.current)
|
||||
}
|
||||
}, [lines, typingSpeed, lineDelay])
|
||||
|
||||
return (
|
||||
<pre className={className}>
|
||||
{text}
|
||||
<span style={{ opacity: showCursor ? 1 : 0 }}> ▍</span>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user