What's cooler than Beams? Exploding beams.Exploding beams.
Exploding beams.
Exploding beams.
Installation
Install dependencies
npm i motion clsx tailwind-merge @tabler/icons-react
Add util file
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 "motion/react";
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 |
Build websites faster and 10x better than your competitors with Aceternity UI Pro
With the best in class components and templates, stand out from the crowd and get more attention to your website. Trusted by founders and entrepreneurs from all over the world.
Manu is the man! He is the best front-end developer I have worked with.
He took the requirements and quite literally ran with them. We are super happy with the result and product we go...
John Shahawy
Founder at Rogue and Moonbeam