A customizable glow effect with animation modes, colors, blur, and transitions.
1'use client';
2import { cn } from '@/lib/utils';
3import { motion, Transition } from 'motion/react';
4
5export type GlowEffectProps = {
6 className?: string;
7 style?: React.CSSProperties;
8 colors?: string[];
9 mode?:
10 | 'rotate'
11 | 'pulse'
12 | 'breathe'
13 | 'colorShift'
14 | 'flowHorizontal'
15 | 'static';
16 blur?:
17 | number
18 | 'softest'
19 | 'soft'
20 | 'medium'
21 | 'strong'
22 | 'stronger'
23 | 'strongest'
24 | 'none';
25 transition?: Transition;
26 scale?: number;
27 duration?: number;
28};
29
30export function GlowEffect({
31 className,
32 style,
33 colors = ['#FF5733', '#33FF57', '#3357FF', '#F1C40F'],
34 mode = 'rotate',
35 blur = 'medium',
36 transition,
37 scale = 1,
38 duration = 5,
39}: GlowEffectProps) {
40 const BASE_TRANSITION = {
41 repeat: Infinity,
42 duration: duration,
43 ease: 'linear',
44 };
45
46 const animations = {
47 rotate: {
48 background: [
49 `conic-gradient(from 0deg at 50% 50%, ${colors.join(', ')})`,
50 `conic-gradient(from 360deg at 50% 50%, ${colors.join(', ')})`,
51 ],
52 transition: {
53 ...(transition ?? BASE_TRANSITION),
54 },
55 },
56 pulse: {
57 background: colors.map(
58 (color) =>
59 `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)`
60 ),
61 scale: [1 * scale, 1.1 * scale, 1 * scale],
62 opacity: [0.5, 0.8, 0.5],
63 transition: {
64 ...(transition ?? {
65 ...BASE_TRANSITION,
66 repeatType: 'mirror',
67 }),
68 },
69 },
70 breathe: {
71 background: [
72 ...colors.map(
73 (color) =>
74 `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)`
75 ),
76 ],
77 scale: [1 * scale, 1.05 * scale, 1 * scale],
78 transition: {
79 ...(transition ?? {
80 ...BASE_TRANSITION,
81 repeatType: 'mirror',
82 }),
83 },
84 },
85 colorShift: {
86 background: colors.map((color, index) => {
87 const nextColor = colors[(index + 1) % colors.length];
88 return `conic-gradient(from 0deg at 50% 50%, ${color} 0%, ${nextColor} 50%, ${color} 100%)`;
89 }),
90 transition: {
91 ...(transition ?? {
92 ...BASE_TRANSITION,
93 repeatType: 'mirror',
94 }),
95 },
96 },
97 flowHorizontal: {
98 background: colors.map((color) => {
99 const nextColor = colors[(colors.indexOf(color) + 1) % colors.length];
100 return `linear-gradient(to right, ${color}, ${nextColor})`;
101 }),
102 transition: {
103 ...(transition ?? {
104 ...BASE_TRANSITION,
105 repeatType: 'mirror',
106 }),
107 },
108 },
109 static: {
110 background: `linear-gradient(to right, ${colors.join(', ')})`,
111 },
112 };
113
114 const getBlurClass = (blur: GlowEffectProps['blur']) => {
115 if (typeof blur === 'number') {
116 return `blur-[${blur}px]`;
117 }
118
119 const presets = {
120 softest: 'blur-xs',
121 soft: 'blur-sm',
122 medium: 'blur-md',
123 strong: 'blur-lg',
124 stronger: 'blur-xl',
125 strongest: 'blur-xl',
126 none: 'blur-none',
127 };
128
129 return presets[blur as keyof typeof presets];
130 };
131
132 return (
133 <motion.div
134 style={
135 {
136 ...style,
137 '--scale': scale,
138 willChange: 'transform',
139 backfaceVisibility: 'hidden',
140 } as React.CSSProperties
141 }
142 animate={animations[mode]}
143 className={cn(
144 'pointer-events-none absolute inset-0 h-full w-full',
145 'scale-[var(--scale)] transform-gpu',
146 getBlurClass(blur),
147 className
148 )}
149 />
150 );
151}
152