霓虹边框 仿gemini生成

17 阅读1分钟

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