'use client'
import * as React from 'react'
import { interpolate } from 'flubber'
import { cn } from '@/lib/utils'
const SHAPES = [
{
viewBox: '0 0 33 34',
d: 'M14.9683 0.839776C15.6732 -0.279924 17.3268 -0.279926 18.0317 0.839775L19.7214 3.52373C20.1848 4.25987 21.1197 4.55915 21.9338 4.23198L24.9021 3.0391C26.1404 2.54146 27.4781 3.49904 27.3804 4.81311L27.1461 7.96297C27.0819 8.82689 27.6597 9.61042 28.5135 9.81718L31.6265 10.571C32.9252 10.8855 33.4362 12.4349 32.5732 13.4414L30.5044 15.854C29.937 16.5158 29.937 17.4843 30.5044 18.146L32.5732 20.5586C33.4362 21.5651 32.9252 23.1145 31.6265 23.429L28.5135 24.1828C27.6597 24.3896 27.0819 25.1731 27.1461 26.037L27.3804 29.1869C27.4781 30.501 26.1404 31.4586 24.9021 30.9609L21.9338 29.768C21.1197 29.4408 20.1848 29.7401 19.7214 30.4763L18.0317 33.1602C17.3268 34.2799 15.6732 34.2799 14.9683 33.1602L13.2786 30.4763C12.8152 29.7401 11.8803 29.4408 11.0662 29.768L8.09794 30.9609C6.85964 31.4585 5.52187 30.501 5.6196 29.1869L5.85388 26.037C5.91814 25.1731 5.34033 24.3896 4.48651 24.1828L1.37349 23.429C0.0747851 23.1145 -0.4362 21.5651 0.426845 20.5586L2.49559 18.146C3.06299 17.4843 3.06299 16.5158 2.49559 15.854L0.426846 13.4414C-0.436199 12.4349 0.0747831 10.8855 1.37348 10.571L4.48651 9.81718C5.34033 9.61042 5.91814 8.82689 5.85388 7.96297L5.6196 4.81311C5.52187 3.49904 6.85964 2.54145 8.09794 3.0391L11.0662 4.23198C11.8803 4.55915 12.8152 4.25987 13.2786 3.52374L14.9683 0.839776Z',
},
{
viewBox: '0 0 34 34',
d: 'M13.3091 1.40363C13.4924 1.254 13.584 1.17919 13.6677 1.11603C15.6389 -0.372011 18.3611 -0.372011 20.3323 1.11603C20.416 1.17919 20.5076 1.254 20.6909 1.40363C20.7727 1.47042 20.8136 1.50381 20.8541 1.5356C21.7818 2.26445 22.9191 2.67748 24.0993 2.71409C24.1508 2.71569 24.2037 2.71634 24.3094 2.71765C24.5462 2.72059 24.6646 2.72206 24.7694 2.72733C27.2381 2.85162 29.3234 4.59743 29.8748 7.00154C29.8982 7.10358 29.9202 7.21966 29.9642 7.45183C29.9838 7.55547 29.9937 7.60729 30.0042 7.65759C30.2452 8.81087 30.8504 9.85669 31.7309 10.6416C31.7693 10.6759 31.8094 10.7103 31.8895 10.7791C32.069 10.9332 32.1588 11.0102 32.2357 11.0815C34.0467 12.76 34.5194 15.4347 33.393 17.6299C33.3451 17.7231 33.2872 17.8262 33.1714 18.0322C33.1196 18.1242 33.0938 18.1702 33.0694 18.2155C32.5111 19.2536 32.3009 20.4429 32.4697 21.6088C32.4771 21.6597 32.4856 21.7117 32.5027 21.8158C32.5409 22.049 32.56 22.1656 32.573 22.2695C32.879 24.7168 31.5179 27.0689 29.2407 28.0281C29.1441 28.0688 29.0333 28.1106 28.8118 28.1942C28.7129 28.2315 28.6635 28.2501 28.6156 28.2692C27.5192 28.7063 26.592 29.4826 25.9701 30.484C25.943 30.5277 25.916 30.5731 25.862 30.6637C25.741 30.8669 25.6806 30.9685 25.6236 31.0564C24.2814 33.1273 21.7234 34.0563 19.3609 33.3306C19.2606 33.2998 19.1489 33.2608 18.9253 33.1827C18.8256 33.1479 18.7757 33.1305 18.7268 33.1144C17.6052 32.7461 16.3948 32.7461 15.2732 33.1144C15.2243 33.1305 15.1744 33.1479 15.0747 33.1827C14.8511 33.2608 14.7394 33.2998 14.6391 33.3306C12.2767 34.0563 9.71865 33.1273 8.37641 31.0564C8.31944 30.9685 8.25897 30.8669 8.13801 30.6637C8.08402 30.5731 8.05702 30.5277 8.0299 30.484C7.40803 29.4826 6.48083 28.7063 5.38436 28.2692C5.33654 28.2501 5.28709 28.2315 5.18821 28.1942C4.96669 28.1106 4.85593 28.0688 4.75928 28.0281C2.48205 27.0689 1.12097 24.7168 1.42698 22.2695C1.43997 22.1656 1.45908 22.049 1.4973 21.8158C1.51436 21.7117 1.52289 21.6597 1.53026 21.6088C1.69906 20.4429 1.48889 19.2536 0.930563 18.2155C0.906211 18.1702 0.880352 18.1242 0.828635 18.0322C0.712779 17.8262 0.654852 17.7231 0.60704 17.6299C-0.519433 15.4347 -0.0467336 12.76 1.76433 11.0815C1.8412 11.0102 1.93096 10.9332 2.11047 10.7791C2.19061 10.7103 2.23068 10.6759 2.26908 10.6416C3.14958 9.85669 3.75476 8.81087 3.99583 7.6576C4.00634 7.60729 4.01617 7.55547 4.03582 7.45183C4.07984 7.21966 4.10185 7.10357 4.12525 7.00153C4.67662 4.59743 6.76192 2.85162 9.23063 2.72733C9.33541 2.72206 9.45381 2.72059 9.69062 2.71765C9.79633 2.71634 9.84919 2.71569 9.90067 2.71409C11.0809 2.67748 12.2182 2.26445 13.1459 1.5356C13.1864 1.50381 13.2273 1.47042 13.3091 1.40363Z',
},
{
viewBox: '0 0 34 32',
d: 'M11.6481 2.43735C13.3067 1.22903 14.136 0.624865 15.022 0.325145C16.3035 -0.108382 17.6965 -0.108382 18.978 0.325145C19.864 0.624865 20.6933 1.22903 22.3519 2.43735L25.9794 5.08012L29.6049 7.5564C31.3249 8.73116 32.1849 9.31853 32.7598 10.0573C33.5914 11.1258 34.0282 12.4391 33.9986 13.7824C33.9781 14.711 33.6363 15.6817 32.9526 17.6232L31.529 21.6657L30.215 25.808C29.5983 27.7519 29.29 28.7239 28.7509 29.48C27.9712 30.5736 26.838 31.378 25.5341 31.7634C24.6327 32.0299 23.5936 32.0144 21.5154 31.9835L17 31.9162L12.4846 31.9835C10.4064 32.0144 9.36728 32.0299 8.46588 31.7634C7.16204 31.378 6.02882 30.5736 5.24909 29.48C4.71002 28.7239 4.40169 27.7519 3.78503 25.808L2.47101 21.6657L1.04741 17.6232C0.363716 15.6817 0.021869 14.711 0.00141072 13.7824C-0.0281818 12.4391 0.408555 11.1258 1.24017 10.0573C1.8151 9.31853 2.67508 8.73116 4.39505 7.5564L8.02059 5.08012L11.6481 2.43735Z',
},
{
viewBox: '0 0 30 30',
d: 'M7.98566 3.77706C13.0217 -1.25902 21.1869 -1.25902 26.2229 3.77706C31.259 8.81315 31.259 16.9783 26.2229 22.0143L22.0143 26.2229C16.9783 31.259 8.81315 31.259 3.77706 26.2229C-1.25902 21.1869 -1.25902 13.0217 3.77706 7.98566L7.98566 3.77706Z',
},
{
viewBox: '0 0 34 34',
d: 'M26.1856 4.12375C27.2431 4.19561 27.7718 4.23154 28.1991 4.41844C28.8175 4.68887 29.3111 5.18252 29.5816 5.80088C29.7685 6.22823 29.8044 6.75694 29.8762 7.81438L30.0402 10.2275C30.0693 10.6552 30.0838 10.869 30.1303 11.0733C30.1975 11.3684 30.3142 11.6501 30.4754 11.9063C30.5869 12.0836 30.7279 12.2451 31.0097 12.5681L32.6001 14.3903C33.297 15.1889 33.6455 15.5881 33.8155 16.0225C34.0615 16.6509 34.0615 17.3491 33.8155 17.9775C33.6455 18.4119 33.297 18.8111 32.6001 19.6097L31.0097 21.4319C30.7279 21.7549 30.5869 21.9164 30.4754 22.0937C30.3142 22.3499 30.1975 22.6316 30.1303 22.9267C30.0838 23.131 30.0693 23.3448 30.0402 23.7725L29.8762 26.1856C29.8044 27.2431 29.7685 27.7718 29.5816 28.1991C29.3111 28.8175 28.8175 29.3111 28.1991 29.5816C27.7718 29.7685 27.2431 29.8044 26.1856 29.8762L23.7725 30.0402C23.3448 30.0693 23.131 30.0838 22.9267 30.1303C22.6316 30.1975 22.3499 30.3142 22.0937 30.4754C21.9164 30.5869 21.7549 30.7279 21.4319 31.0097L19.6097 32.6001C18.8111 33.297 18.4119 33.6455 17.9775 33.8155C17.3491 34.0615 16.6509 34.0615 16.0225 33.8155C15.5881 33.6455 15.1889 33.297 14.3903 32.6001L12.5681 31.0097C12.2451 30.7279 12.0836 30.5869 11.9063 30.4754C11.6501 30.3142 11.3684 30.1975 11.0733 30.1303C10.869 30.0838 10.6552 30.0693 10.2275 30.0402L7.81438 29.8762C6.75694 29.8044 6.22822 29.7685 5.80088 29.5816C5.18252 29.3111 4.68887 28.8175 4.41844 28.1991C4.23154 27.7718 4.19561 27.2431 4.12375 26.1856L3.95977 23.7725C3.93071 23.3448 3.91618 23.131 3.86969 22.9267C3.80251 22.6316 3.68584 22.3499 3.52463 22.0937C3.41306 21.9164 3.27213 21.7549 2.99027 21.4319L1.3999 19.6097C0.702996 18.8111 0.354542 18.4119 0.184516 17.9775C-0.0615054 17.3491 -0.0615053 16.6509 0.184516 16.0225C0.354542 15.5881 0.702996 15.1889 1.3999 14.3903L2.99027 12.5681C3.27213 12.2451 3.41306 12.0836 3.52463 11.9063C3.68584 11.6501 3.80251 11.3684 3.86969 11.0733C3.91618 10.869 3.93071 10.6552 3.95977 10.2275L4.12375 7.81438C4.19561 6.75694 4.23154 6.22823 4.41844 5.80088C4.68887 5.18252 5.18252 4.68887 5.80088 4.41844C6.22823 4.23154 6.75694 4.19561 7.81438 4.12375L10.2275 3.95977C10.6552 3.93071 10.869 3.91618 11.0733 3.86969C11.3684 3.80251 11.6501 3.68584 11.9063 3.52463C12.0836 3.41306 12.2451 3.27213 12.5681 2.99027L14.3903 1.3999C15.1889 0.702996 15.5881 0.354542 16.0225 0.184516C16.6509 -0.0615054 17.3491 -0.0615054 17.9775 0.184516C18.4119 0.354542 18.8111 0.702996 19.6097 1.3999L21.4319 2.99027C21.7549 3.27213 21.9164 3.41306 22.0937 3.52463C22.3499 3.68584 22.6316 3.80251 22.9267 3.86969C23.131 3.91618 23.3448 3.93071 23.7725 3.95977L26.1856 4.12375Z',
},
{
viewBox: '0 0 28 28',
d: 'M17.8729 0.620696C23.8872 -1.99138 29.9914 4.11282 27.3793 10.1271L26.9474 11.1214C26.1499 12.9576 26.1499 15.0424 26.9474 16.8786L27.3793 17.8729C29.9914 23.8872 23.8872 29.9914 17.8729 27.3793L16.8786 26.9474C15.0424 26.1499 12.9576 26.1499 11.1214 26.9474L10.1271 27.3793C4.11281 29.9914 -1.99138 23.8872 0.620698 17.8729L1.05257 16.8786C1.85006 15.0424 1.85005 12.9576 1.05257 11.1214L0.620696 10.1271C-1.99138 4.11281 4.11282 -1.99138 10.1271 0.620698L11.1214 1.05257C12.9576 1.85005 15.0424 1.85005 16.8786 1.05257L17.8729 0.620696Z',
},
{
viewBox: '0 0 30 30',
d: 'M23.1309 23.1309C16.1705 30.0913 6.88765 32.0935 2.39707 27.6029C-2.09352 23.1123 -0.0913343 13.8295 6.86908 6.86908C13.8295 -0.0913329 23.1123 -2.09352 27.6029 2.39707C32.0935 6.88765 30.0913 16.1705 23.1309 23.1309Z',
},
]
const customEase = (() => {
const x1 = 0.72,
y1 = 0.18,
x2 = 0.64,
y2 = 0.95
return (t: number) => {
if (t === 0) return 0
if (t === 1) return 1
const oneMinusT = 1 - t
return 3 * oneMinusT * oneMinusT * t * y1 + 3 * oneMinusT * t * t * y2 + t * t * t
}
})()
const interpolatorCache = new Map<string, (t: number) => string>()
const parseViewBox = (vb: string) => vb.split(' ').map(Number)
const lerp = (start: number, end: number, t: number) => start + (end - start) * t
interface LoadingProps extends React.HTMLAttributes<HTMLDivElement> {
contained?: boolean
duration?: number
}
export function Loading({ duration = 680, contained, className, ...props }: LoadingProps) {
const svgRef = React.useRef<SVGSVGElement>(null)
const pathRef = React.useRef<SVGPathElement>(null)
const [index, setIndex] = React.useState(0)
const [baseRotation, setBaseRotation] = React.useState(0)
const currentShape = SHAPES[index]
const nextShape = SHAPES[(index + 1) % SHAPES.length]
const stepRotation = 180
const interpolator = React.useMemo(() => {
const nextIndex = (index + 1) % SHAPES.length
const cacheKey = `${index}-${nextIndex}`
if (interpolatorCache.has(cacheKey)) {
return interpolatorCache.get(cacheKey)!
}
const newInterpolator = interpolate(currentShape.d, nextShape.d, {
maxSegmentLength: 1,
})
interpolatorCache.set(cacheKey, newInterpolator)
return newInterpolator
}, [index, currentShape.d, nextShape.d])
React.useEffect(() => {
let animationFrameId: number
let startTime: number | null = null
const animate = (time: number) => {
if (startTime === null) startTime = time
const elapsed = time - startTime
const rawProgress = Math.min(elapsed / duration, 1)
const progress = customEase(rawProgress)
// 1. 更新路径 (Path Morphing)
if (pathRef.current) {
pathRef.current.setAttribute('d', interpolator(progress))
}
// 2. 更新 ViewBox
if (svgRef.current) {
const startVB = parseViewBox(currentShape.viewBox)
const endVB = parseViewBox(nextShape.viewBox)
const currentVB = startVB.map((val, i) => lerp(val, endVB[i], progress))
svgRef.current.setAttribute('viewBox', currentVB.join(' '))
// 3. 更新旋转 (Rotate)
const currentRotation = baseRotation + stepRotation * progress
// 4. 更新缩放 (Scale) - 逻辑: 0->0.8 (放大), 0.8->1 (回缩)
// 原逻辑: [0, 0.8, 1], [1, 1.14, 1]
let currentScale = 1
if (progress < 0.8) {
// 0 -> 0.8 映射到 1 -> 1.14
currentScale = 1 + 0.14 * (progress / 0.8)
} else {
// 0.8 -> 1 映射到 1.14 -> 1
currentScale = 1.14 - 0.14 * ((progress - 0.8) / 0.2)
}
// 直接应用样式以获得最佳性能
svgRef.current.style.transform = `rotate(${currentRotation}deg) scale(${currentScale})`
}
if (rawProgress < 1) {
animationFrameId = requestAnimationFrame(animate)
} else {
setIndex(prev => (prev + 1) % SHAPES.length)
setBaseRotation(prev => prev + stepRotation)
}
}
animationFrameId = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(animationFrameId)
}
}, [index, duration, interpolator, baseRotation, currentShape.viewBox, nextShape.viewBox])
return (
<div
className={cn(
'relative flex size-12 items-center justify-center rounded-full p-2.5',
contained && 'bg-primary-container',
className,
)}
{...props}
>
<svg
ref={svgRef}
viewBox={currentShape.viewBox}
className={cn('text-primary will-change-transform')}
fill='currentColor'
style={{
transform: `rotate(${baseRotation}deg) scale(1)`,
}}
>
<path ref={pathRef} d={currentShape.d} />
</svg>
</div>
)
}