跳到正文
Motif 母题

文字漩涡

一圈圈缓缓旋转的文字环,指针经过时字形溶解成飘散的尘埃,点击则把尘埃吸向一点。文案可自定义,深浅两种底色。适合做品牌页、作品集的氛围背景。

正在载入预览

调参

预设

内容
外观
1.21
1.00
动效
1.00×
交互
1.00
1.00
920 ms
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Meng To
// Source: https://github.com/MengTo/threeui/blob/68802d5/src/shaders/typography-vortex/TypographyVortexCanvas.tsx
// Modified by Motif; see the item's provenance.
import { cn, useFrameLoop, usePrefersReducedMotion } from '@motif/runtime'
import { useEffect, useRef, type CSSProperties, type ReactNode } from 'react'
import { createTypographyVortexRenderer } from './vortex-renderer'
/* @motif:defaults */
export const defaults = {
phrase: 'MOTIF / DESIGN IN MOTION / ',
mode: 'dark',
speed: 1,
ringGrowth: 1.21,
opacity: 1,
dissolveRadius: 1,
particleAmount: 1,
suctionDuration: 920,
}
/* @motif:end */
export type TypographyVortexProps = Partial<typeof defaults> & {
className?: string
style?: CSSProperties
children?: ReactNode
}
/** 一圈圈旋转的文字环,指针经过时溶解成尘埃,点击时被吸向一点。 */
export function TypographyVortex({ className, style, children, ...props }: TypographyVortexProps) {
const reduced = usePrefersReducedMotion()
const options = { ...defaults, ...props }
const optionsRef = useRef({ ...options, mode: options.mode === 'light' ? ('light' as const) : ('dark' as const), reduced })
optionsRef.current = { ...options, mode: options.mode === 'light' ? 'light' : 'dark', reduced }
const hostRef = useRef<HTMLDivElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const rendererRef = useRef<ReturnType<typeof createTypographyVortexRenderer> | null>(null)
useEffect(() => {
const renderer = createTypographyVortexRenderer(hostRef.current!, canvasRef.current!, () => optionsRef.current)
rendererRef.current = renderer
return () => {
rendererRef.current = null
renderer.dispose()
}
}, [])
// 速度只作用于环的旋转(渲染器内部乘 speed),粒子按真实时间运动,所以循环本身不变速。
useFrameLoop(hostRef, ({ time }) => rendererRef.current?.render(time * 1000), { reducedMotion: 'static', staticTime: 6 })
return (
<div ref={hostRef} className={cn('relative isolate overflow-hidden', className)} style={style}>
<canvas ref={canvasRef} role="img" aria-label={options.phrase} className="absolute inset-0 -z-10 block h-full w-full" />
{children}
</div>
)
}

同类条目

更多文字 →