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 |
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
I'd highly recommend working with Manu on a site redesign. We came to Manu with a basic website shell. Manu quickly took our rough concept and transformed it into a polished, user-friendly website....
Ray Thai
Head of Product at Fireworks