import React, { memo, ReactNode, CSSProperties, useId, useEffect, useRef, useState } from 'react'
/**
- 霓虹灯流光边框组件
- SVG 实现 - 蓝紫色连续渐变 + 柔和光晕 */
interface NeonGlowBorderProps { children?: ReactNode enabled?: boolean show?: boolean borderWidth?: number borderRadius?: number animationDuration?: number glowIntensity?: number blueColor?: string purpleColor?: string style?: CSSProperties className?: string }
const NeonGlowBorder: React.FC = memo( ({ children, enabled = true, show = true, borderWidth = 2, borderRadius = 12, animationDuration = 4, glowIntensity = 1, blueColor = '#5295FA', purpleColor = '#A855F7', style, className }) => { const uniqueId = useId().replace(/:/g, '') const containerRef = useRef(null) const [size, setSize] = useState({ width: 0, height: 0 })
useEffect(() => {
if (!containerRef.current) return
const updateSize = () => {
if (containerRef.current) {
setSize({
width: containerRef.current.offsetWidth,
height: containerRef.current.offsetHeight
})
}
}
updateSize()
const ro = new ResizeObserver(updateSize)
ro.observe(containerRef.current)
return () => ro.disconnect()
}, [])
const { width, height } = size
const r = Math.min(borderRadius, width / 2, height / 2)
// 圆角矩形路径
const pathD = width > 0 && height > 0
? `M ${r} 0 L ${width - r} 0 Q ${width} 0 ${width} ${r} L ${width} ${height - r} Q ${width} ${height} ${width - r} ${height} L ${r} ${height} Q 0 ${height} 0 ${height - r} L 0 ${r} Q 0 0 ${r} 0 Z`
: ''
// 周长
const perimeter = width > 0 && height > 0
? 2 * (width + height - 4 * r) + 2 * Math.PI * r
: 1000
// 四段渐变:蓝亮(45%) → 暗过渡(5%) → 紫亮(45%) → 暗过渡(5%)
const seg1 = perimeter * 0.45 // 蓝色
const seg2 = perimeter * 0.05 // 暗过渡
const seg3 = perimeter * 0.45 // 紫色
const seg4 = perimeter * 0.05 // 暗过渡
return (
<div
ref={containerRef}
className={className}
style={{
position: 'relative',
width: '100%',
height: '100%',
...style
}}
>
{/* 内容 */}
<div style={{
position: 'relative',
width: '100%',
height: '100%',
borderRadius: `${borderRadius}px`,
overflow: 'hidden',
zIndex: 1
}}>
{children}
</div>
{/* SVG 边框和发光 */}
{width > 0 && height > 0 && (
<svg
width={width + 60 * glowIntensity}
height={height + 60 * glowIntensity}
style={{
position: 'absolute',
top: -30 * glowIntensity,
left: -30 * glowIntensity,
pointerEvents: 'none',
zIndex: 9999,
opacity: show ? 1 : 0,
transition: 'opacity 0.3s ease',
overflow: 'visible'
}}
>
<defs>
{/* 发光滤镜 - 柔和 */}
<filter id={`glow1_${uniqueId}`} x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation={3 * glowIntensity} result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* 发光滤镜 - 中层 */}
<filter id={`glow2_${uniqueId}`} x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation={8 * glowIntensity} result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="blur" />
</feMerge>
</filter>
{/* 发光滤镜 - 外层 */}
<filter id={`glow3_${uniqueId}`} x="-150%" y="-150%" width="400%" height="400%">
<feGaussianBlur stdDeviation={15 * glowIntensity} result="blur" />
<feMerge>
<feMergeNode in="blur" />
</feMerge>
</filter>
</defs>
<g transform={`translate(${30 * glowIntensity}, ${30 * glowIntensity})`}>
{/* ===== 蓝色段 (0% - 45%) ===== */}
{/* 蓝色 - 最外层发光 */}
<path
d={pathD}
fill="none"
stroke={blueColor}
strokeWidth={borderWidth * 3}
strokeLinecap="round"
strokeDasharray={`${seg1} ${perimeter - seg1}`}
filter={`url(#glow3_${uniqueId})`}
opacity={0.4}
>
<animate
attributeName="stroke-dashoffset"
from="0"
to={`-${perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* 蓝色 - 中层发光 */}
<path
d={pathD}
fill="none"
stroke={blueColor}
strokeWidth={borderWidth * 2}
strokeLinecap="round"
strokeDasharray={`${seg1} ${perimeter - seg1}`}
filter={`url(#glow2_${uniqueId})`}
opacity={0.6}
>
<animate
attributeName="stroke-dashoffset"
from="0"
to={`-${perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* 蓝色 - 主线条 */}
<path
d={pathD}
fill="none"
stroke={blueColor}
strokeWidth={borderWidth}
strokeLinecap="round"
strokeDasharray={`${seg1} ${perimeter - seg1}`}
filter={`url(#glow1_${uniqueId})`}
>
<animate
attributeName="stroke-dashoffset"
from="0"
to={`-${perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* ===== 暗过渡 1 (45% - 50%) ===== */}
<path
d={pathD}
fill="none"
stroke={blueColor}
strokeWidth={borderWidth}
strokeLinecap="round"
strokeDasharray={`${seg2} ${perimeter - seg2}`}
opacity={0.3}
>
<animate
attributeName="stroke-dashoffset"
from={`-${seg1}`}
to={`-${seg1 + perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* ===== 紫色段 (50% - 95%) ===== */}
{/* 紫色 - 最外层发光 */}
<path
d={pathD}
fill="none"
stroke={purpleColor}
strokeWidth={borderWidth * 3}
strokeLinecap="round"
strokeDasharray={`${seg3} ${perimeter - seg3}`}
filter={`url(#glow3_${uniqueId})`}
opacity={0.4}
>
<animate
attributeName="stroke-dashoffset"
from={`-${seg1 + seg2}`}
to={`-${seg1 + seg2 + perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* 紫色 - 中层发光 */}
<path
d={pathD}
fill="none"
stroke={purpleColor}
strokeWidth={borderWidth * 2}
strokeLinecap="round"
strokeDasharray={`${seg3} ${perimeter - seg3}`}
filter={`url(#glow2_${uniqueId})`}
opacity={0.6}
>
<animate
attributeName="stroke-dashoffset"
from={`-${seg1 + seg2}`}
to={`-${seg1 + seg2 + perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* 紫色 - 主线条 */}
<path
d={pathD}
fill="none"
stroke={purpleColor}
strokeWidth={borderWidth}
strokeLinecap="round"
strokeDasharray={`${seg1} ${perimeter - seg1}`}
filter={`url(#glow1_${uniqueId})`}
>
<animate
attributeName="stroke-dashoffset"
from={`-${seg1 + seg2}`}
to={`-${seg1 + seg2 + perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
{/* ===== 暗过渡 2 (95% - 100%) ===== */}
<path
d={pathD}
fill="none"
stroke={purpleColor}
strokeWidth={borderWidth}
strokeLinecap="round"
strokeDasharray={`${seg4} ${perimeter - seg4}`}
opacity={0.3}
>
<animate
attributeName="stroke-dashoffset"
from={`-${seg1 + seg2 + seg3}`}
to={`-${seg1 + seg2 + seg3 + perimeter}`}
dur={`${animationDuration}s`}
repeatCount={enabled ? "indefinite" : "0"}
/>
</path>
</g>
</svg>
)}
</div>
)
}
)
NeonGlowBorder.displayName = 'NeonGlowBorder'
export default NeonGlowBorder