Animated Beam

An animated beam of light traveling along a path, perfect for showcasing website integration features

Installation

npm install framer-motion
beam.tsx
1
'use client';
2
3
import { cn } from '@/lib/utils';
4
import { motion } from 'framer-motion';
5
import { forwardRef, RefObject, useEffect, useId, useState } from 'react';
6
7
export interface AnimatedBeamProps {
8
className?: string;
9
containerRef: RefObject<HTMLElement>; // Container ref
10
fromRef: RefObject<HTMLElement>;
11
toRef: RefObject<HTMLElement>;
12
curvature?: number;
13
reverse?: boolean;
14
pathColor?: string;
15
pathWidth?: number;
16
pathOpacity?: number;
17
gradientStartColor?: string;
18
gradientStopColor?: string;
19
delay?: number;
20
duration?: number;
21
startXOffset?: number;
22
startYOffset?: number;
23
endXOffset?: number;
24
endYOffset?: number;
25
dotted?: boolean;
26
dotSpacing?: number;
27
}
28
29
export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
30
className,
31
containerRef,
32
fromRef,
33
toRef,
34
curvature = 0,
35
reverse = false, // Include the reverse prop
36
duration = Math.random() * 3 + 4,
37
delay = 0,
38
pathColor = 'gray',
39
pathWidth = 2,
40
pathOpacity = 0.2,
41
gradientStartColor = '#4d40ff',
42
gradientStopColor = '#4043ff',
43
startXOffset = 0,
44
startYOffset = 0,
45
endXOffset = 0,
46
endYOffset = 0,
47
dotted = false,
48
dotSpacing = 6,
49
}) => {
50
const id = useId();
51
const [pathD, setPathD] = useState('');
52
53
const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
54
const strokeDasharray = dotted ? `${dotSpacing} ${dotSpacing}` : 'none';
55
// Calculate the gradient coordinates based on the reverse prop
56
const gradientCoordinates = reverse
57
? {
58
x1: ['90%', '-10%'],
59
x2: ['100%', '0%'],
60
y1: ['0%', '0%'],
61
y2: ['0%', '0%'],
62
}
63
: {
64
x1: ['10%', '110%'],
65
x2: ['0%', '100%'],
66
y1: ['0%', '0%'],
67
y2: ['0%', '0%'],
68
};
69
70
useEffect(() => {
71
const updatePath = () => {
72
if (containerRef.current && fromRef.current && toRef.current) {
73
const containerRect = containerRef.current.getBoundingClientRect();
74
const rectA = fromRef.current.getBoundingClientRect();
75
const rectB = toRef.current.getBoundingClientRect();
76
77
const svgWidth = containerRect.width;
78
const svgHeight = containerRect.height;
79
setSvgDimensions({ width: svgWidth, height: svgHeight });
80
81
const startX =
82
rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
83
const startY =
84
rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
85
const endX =
86
rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
87
const endY =
88
rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
89
90
const controlY = startY - curvature;
91
const d = `M ${startX},${startY} Q ${
92
(startX + endX) / 2
93
},${controlY} ${endX},${endY}`;
94
setPathD(d);
95
}
96
};
97
98
// Initialize ResizeObserver
99
const resizeObserver = new ResizeObserver((entries) => {
100
// For all entries, recalculate the path
101
for (let entry of entries) {
102
updatePath();
103
}
104
});
105
106
// Observe the container element
107
if (containerRef.current) {
108
resizeObserver.observe(containerRef.current);
109
}
110
111
// Call the updatePath initially to set the initial path
112
updatePath();
113
114
// Clean up the observer on component unmount
115
return () => {
116
resizeObserver.disconnect();
117
};
118
}, [
119
containerRef,
120
fromRef,
121
toRef,
122
curvature,
123
startXOffset,
124
startYOffset,
125
endXOffset,
126
endYOffset,
127
]);
128
129
return (
130
<svg
131
fill='none'
132
width={svgDimensions.width}
133
height={svgDimensions.height}
134
xmlns='http://www.w3.org/2000/svg'
135
className={cn(
136
'pointer-events-none absolute left-0 top-0 transform-gpu stroke-2',
137
className
138
)}
139
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
140
>
141
<path
142
d={pathD}
143
stroke={pathColor}
144
strokeWidth={pathWidth}
145
strokeOpacity={pathOpacity}
146
strokeLinecap='round'
147
strokeDasharray={strokeDasharray}
148
/>
149
<motion.path
150
d={pathD}
151
stroke={`url(#${id})`}
152
strokeLinecap='round'
153
strokeDasharray={strokeDasharray}
154
initial={{
155
strokeWidth: pathWidth,
156
strokeOpacity: 0,
157
}}
158
animate={{
159
strokeWidth: pathWidth * 1.5, // or any scale factor you prefer
160
strokeOpacity: 1,
161
}}
162
transition={{
163
duration: 2, // adjust as needed
164
delay: delay, // use the same delay as the gradient animation
165
}}
166
/>
167
<defs>
168
<motion.linearGradient
169
className='transform-gpu'
170
id={id}
171
gradientUnits={'userSpaceOnUse'}
172
initial={{
173
x1: '0%',
174
x2: '0%',
175
y1: '0%',
176
y2: '0%',
177
}}
178
animate={{
179
x1: gradientCoordinates.x1,
180
x2: gradientCoordinates.x2,
181
y1: gradientCoordinates.y1,
182
y2: gradientCoordinates.y2,
183
}}
184
transition={{
185
delay,
186
duration,
187
ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
188
repeat: Infinity,
189
repeatDelay: 0,
190
}}
191
>
192
<stop stopColor={gradientStartColor} stopOpacity='0'></stop>
193
<stop stopColor={gradientStartColor}></stop>
194
<stop offset='32.5%' stopColor={gradientStopColor}></stop>
195
<stop
196
offset='100%'
197
stopColor={gradientStopColor}
198
stopOpacity='0'
199
></stop>
200
</motion.linearGradient>
201
</defs>
202
</svg>
203
);
204
};
205
206
export const Circle = forwardRef<
207
HTMLDivElement,
208
{ className?: string; children?: React.ReactNode }
209
>(({ className, children }, ref) => {
210
return (
211
<div
212
ref={ref}
213
className={cn(
214
'z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]',
215
className
216
)}
217
>
218
{children}
219
</div>
220
);
221
});

Props

PropTypeDescriptionDefault
classNamestringThe class name for the SVG element.-
containerRefRefObjectThe ref for the container element.-
fromRefRefObjectThe ref of the element from which the beam should start.-
toRefRefObjectThe ref of the element to which the beam should end.-
curvaturenumberThe curvature of the beam.0
reversebooleanWhether the beam should be reversed.false
durationnumberThe duration of the beam animation.Random (4-7)
delaynumberThe delay before the beam animation starts.0
pathColorstringThe color of the beam path."gray"
pathWidthnumberThe width of the beam path.2
pathOpacitynumberThe opacity of the beam path.0.2
gradientStartColorstringThe start color of the gradient for the beam."#4d40ff"
gradientStopColorstringThe stop color of the gradient for the beam."#4043ff"
startXOffsetnumberThe x offset of the beam's start position.0
startYOffsetnumberThe y offset of the beam's start position.0
endXOffsetnumberThe x offset of the beam's end position.0
endYOffsetnumberThe y offset of the beam's end position.0
dottedbooleanWhether the beam should be dotted.false
dotSpacingnumberThe spacing between dots if the beam is dotted.6

Example

Bidrectional

Unidrectional

Multiple Input

Multiple Output