71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { BaseEdge, getBezierPath, type EdgeProps } from "@xyflow/react";
|
|
|
|
// 커스텀 애니메이션 엣지 — bezier 곡선 + 흐르는 파티클 + 글로우 레이어
|
|
export function AnimatedFlowEdge({
|
|
id,
|
|
sourceX,
|
|
sourceY,
|
|
targetX,
|
|
targetY,
|
|
sourcePosition,
|
|
targetPosition,
|
|
style,
|
|
markerEnd,
|
|
data,
|
|
}: EdgeProps) {
|
|
const [edgePath] = getBezierPath({
|
|
sourceX,
|
|
sourceY,
|
|
sourcePosition,
|
|
targetX,
|
|
targetY,
|
|
targetPosition,
|
|
});
|
|
|
|
const strokeColor = (style?.stroke as string) || "hsl(var(--primary))";
|
|
const strokeW = (style?.strokeWidth as number) || 2;
|
|
const isActive = data?.active !== false;
|
|
const duration: string = typeof data?.duration === "string" ? data.duration : "3s";
|
|
const filterId = `edge-glow-${id}`;
|
|
|
|
return (
|
|
<>
|
|
{/* 글로우용 SVG 필터 정의 (엣지별 고유 ID) */}
|
|
<defs>
|
|
<filter id={filterId} x="-50%" y="-50%" width="200%" height="200%">
|
|
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
|
|
<feMerge>
|
|
<feMergeNode in="blur" />
|
|
<feMergeNode in="SourceGraphic" />
|
|
</feMerge>
|
|
</filter>
|
|
</defs>
|
|
{/* 글로우 레이어 */}
|
|
<path
|
|
d={edgePath}
|
|
fill="none"
|
|
stroke={strokeColor}
|
|
strokeWidth={strokeW + 4}
|
|
strokeOpacity={0.12}
|
|
filter={`url(#${filterId})`}
|
|
/>
|
|
{/* 메인 엣지 */}
|
|
<BaseEdge id={id} path={edgePath} style={style} markerEnd={markerEnd} />
|
|
{/* 흐르는 파티클 */}
|
|
{isActive && (
|
|
<>
|
|
<circle r="3" fill={strokeColor} filter={`url(#${filterId})`}>
|
|
<animateMotion dur={duration} repeatCount="indefinite" path={edgePath} />
|
|
</circle>
|
|
<circle r="1.5" fill="white" opacity="0.85">
|
|
<animateMotion dur={duration} repeatCount="indefinite" path={edgePath} />
|
|
</circle>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
}
|