What's cooler than Beams? Exploding beams.Exploding beams.
Exploding beams.
Exploding beams.
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge @tabler/icons-react
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
components/ui/background-beams-with-collision.tsx
"use client";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "framer-motion";
import React, { useRef, useState, useEffect } from "react";
export const BackgroundBeamsWithCollision = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const parentRef = useRef<HTMLDivElement>(null);
const beams = [
{
initialX: 10,
translateX: 10,
duration: 7,
repeatDelay: 3,
delay: 2,
},
{
initialX: 600,
translateX: 600,
duration: 3,
repeatDelay: 3,
delay: 4,
},
{
initialX: 100,
translateX: 100,
duration: 7,
repeatDelay: 7,
className: "h-6",
},
{
initialX: 400,
translateX: 400,
duration: 5,
repeatDelay: 14,
delay: 4,
},
{
initialX: 800,
translateX: 800,
duration: 11,
repeatDelay: 2,
className: "h-20",
},
{
initialX: 1000,
translateX: 1000,
duration: 4,
repeatDelay: 2,
className: "h-12",
},
{
initialX: 1200,
translateX: 1200,
duration: 6,
repeatDelay: 4,
delay: 2,
className: "h-6",
},
];
return (
<div
ref={parentRef}
className={cn(
"h-96 md:h-[40rem] bg-gradient-to-b from-white to-neutral-100 dark:from-neutral-950 dark:to-neutral-800 relative flex items-center w-full justify-center overflow-hidden",
// h-screen if you want bigger
className
)}
>
{beams.map((beam) => (
<CollisionMechanism
key={beam.initialX + "beam-idx"}
beamOptions={beam}
containerRef={containerRef}
parentRef={parentRef}
/>
))}
{children}
<div
ref={containerRef}
className="absolute bottom-0 bg-neutral-100 w-full inset-x-0 pointer-events-none"
style={{
boxShadow:
"0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset",
}}
></div>
</div>
);
};
const CollisionMechanism = React.forwardRef<
HTMLDivElement,
{
containerRef: React.RefObject<HTMLDivElement>;
parentRef: React.RefObject<HTMLDivElement>;
beamOptions?: {
initialX?: number;
translateX?: number;
initialY?: number;
translateY?: number;
rotate?: number;
className?: string;
duration?: number;
delay?: number;
repeatDelay?: number;
};
}
>(({ parentRef, containerRef, beamOptions = {} }, ref) => {
const beamRef = useRef<HTMLDivElement>(null);
const [collision, setCollision] = useState<{
detected: boolean;
coordinates: { x: number; y: number } | null;
}>({
detected: false,
coordinates: null,
});
const [beamKey, setBeamKey] = useState(0);
const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false);
useEffect(() => {
const checkCollision = () => {
if (
beamRef.current &&
containerRef.current &&
parentRef.current &&
!cycleCollisionDetected
) {
const beamRect = beamRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
const parentRect = parentRef.current.getBoundingClientRect();
if (beamRect.bottom >= containerRect.top) {
const relativeX =
beamRect.left - parentRect.left + beamRect.width / 2;
const relativeY = beamRect.bottom - parentRect.top;
setCollision({
detected: true,
coordinates: {
x: relativeX,
y: relativeY,
},
});
setCycleCollisionDetected(true);
}
}
};
const animationInterval = setInterval(checkCollision, 50);
return () => clearInterval(animationInterval);
}, [cycleCollisionDetected, containerRef]);
useEffect(() => {
if (collision.detected && collision.coordinates) {
setTimeout(() => {
setCollision({ detected: false, coordinates: null });
setCycleCollisionDetected(false);
}, 2000);
setTimeout(() => {
setBeamKey((prevKey) => prevKey + 1);
}, 2000);
}
}, [collision]);
return (
<>
<motion.div
key={beamKey}
ref={beamRef}
animate="animate"
initial={{
translateY: beamOptions.initialY || "-200px",
translateX: beamOptions.initialX || "0px",
rotate: beamOptions.rotate || 0,
}}
variants={{
animate: {
translateY: beamOptions.translateY || "1800px",
translateX: beamOptions.translateX || "0px",
rotate: beamOptions.rotate || 0,
},
}}
transition={{
duration: beamOptions.duration || 8,
repeat: Infinity,
repeatType: "loop",
ease: "linear",
delay: beamOptions.delay || 0,
repeatDelay: beamOptions.repeatDelay || 0,
}}
className={cn(
"absolute left-0 top-20 m-auto h-14 w-px rounded-full bg-gradient-to-t from-indigo-500 via-purple-500 to-transparent",
beamOptions.className
)}
/>
<AnimatePresence>
{collision.detected && collision.coordinates && (
<Explosion
key={`${collision.coordinates.x}-${collision.coordinates.y}`}
className=""
style={{
left: `${collision.coordinates.x}px`,
top: `${collision.coordinates.y}px`,
transform: "translate(-50%, -50%)",
}}
/>
)}
</AnimatePresence>
</>
);
});
CollisionMechanism.displayName = "CollisionMechanism";
const Explosion = ({ ...props }: React.HTMLProps<HTMLDivElement>) => {
const spans = Array.from({ length: 20 }, (_, index) => ({
id: index,
initialX: 0,
initialY: 0,
directionX: Math.floor(Math.random() * 80 - 40),
directionY: Math.floor(Math.random() * -50 - 10),
}));
return (
<div {...props} className={cn("absolute z-50 h-2 w-2", props.className)}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5, ease: "easeOut" }}
className="absolute -inset-x-10 top-0 m-auto h-2 w-10 rounded-full bg-gradient-to-r from-transparent via-indigo-500 to-transparent blur-sm"
></motion.div>
{spans.map((span) => (
<motion.span
key={span.id}
initial={{ x: span.initialX, y: span.initialY, opacity: 1 }}
animate={{
x: span.directionX,
y: span.directionY,
opacity: 0,
}}
transition={{ duration: Math.random() * 1.5 + 0.5, ease: "easeOut" }}
className="absolute h-1 w-1 rounded-full bg-gradient-to-b from-indigo-500 to-purple-500"
/>
))}
</div>
);
};
Props
BackgroundBeamsWithCollision
Prop Name | Type | Description | Default |
---|---|---|---|
children | React.ReactNode | The content to be rendered inside the component | Required |
className | string? | Additional CSS classes to be applied to the container | undefined |
CollisionMechanism
Prop Name | Type | Description | Default |
---|---|---|---|
containerRef | React.RefObject<HTMLDivElement> | Reference to the container element | Required |
parentRef | React.RefObject<HTMLDivElement> | Reference to the parent element | Required |
beamOptions | object | Configuration options for the beam |
beamOptions
Property | Type | Description | Default |
---|---|---|---|
initialX | number? | Initial X position of the beam | 0 |
translateX | number? | X position to translate to | 0 |
initialY | number? | Initial Y position of the beam | "-200px" |
translateY | number? | Y position to translate to | "1800px" |
rotate | number? | Rotation angle of the beam | 0 |
className | string? | Additional CSS classes for the beam | undefined |
duration | number? | Duration of the animation in seconds | 8 |
delay | number? | Delay before the animation starts in seconds | 0 |
repeatDelay | number? | Delay before the animation repeats in seconds | 0 |