Animated Grid Pattern
Animated Grid Pattern is a React and Tailwind CSS effect component from Eldora UI, licensed MIT. Copy it and paste it into your project.
Eldora UI
MIT
effects
What it is
Animated Grid Pattern is a React and Tailwind CSS effect component from Eldora UI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i react three react-three-fiber
Licence
This component comes from Eldora UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Animated Grid Pattern
"use client"
import React, { useEffect, useMemo, useRef, useState } from "react"
import { Canvas, useFrame, useThree } from "@react-three/fiber"
import { useTheme } from "next-themes"
import { ExtrudeGeometry, Shape } from "three"
import * as THREE from "three"
interface GridProps {
position: [number, number, number]
width?: number
length?: number
cornerRadius?: number
gridPosition: [number, number]
hoveredGrid: [number, number] | null
rippleScale?: number
rippleRadius?: number
}
const Grid = ({
position,
width = 4,
length = 4,
cornerRadius = 2,
gridPosition,
hoveredGrid,
rippleScale = 0.3,
rippleRadius = 3,
}: GridProps) => {
const meshRef = useRef<THREE.Mesh>(null)
const [currentScale, setCurrentScale] = useState(1)
const { theme } = useTheme()
const gridColor = theme === "dark" ? "#232323" : "#e5e7eb"
const geometry = useMemo(() => {
const shape = new Shape()
const angleStep = Math.PI * 0.5
const radius = cornerRadius
const halfWidth = width / 2
const halfLength = length / 2
shape.absarc(
halfWidth - radius,
halfLength - radius,
radius,
angleStep * 0,
angleStep * 1
)
shape.absarc(
-halfWidth + radius,
halfLength - radius,
radius,
angleStep * 1,
angleStep * 2
)
shape.absarc(
-halfWidth + radius,
-halfLength + radius,
radius,
angleStep * 2,
angleStep * 3
)
shape.absarc(
halfWidth - radius,
-halfLength + radius,
radius,
angleStep * 3,
angleStep * 4
)
const extrudeSettings = {
depth: 0.3,
bevelEnabled: true,
bevelThickness: 0.05,
bevelSize: 0.05,
bevelSegments: 20,
curveSegments: 20,
}
const geometry = new ExtrudeGeometry(shape, extrudeSettings)
geometry.center()
return geometry
}, [width, length, cornerRadius])
useEffect(() => {
return () => {
geometry.dispose()
}
}, [geometry])
useFrame(() => {
if (meshRef.current) {
let targetScale = 1
const isThisGridHovered =
hoveredGrid &&
gridPosition[0] === hoveredGrid[0] &&
gridPosition[1] === hoveredGrid[1]
if (isThisGridHovered) {
targetScale = 5
} else if (hoveredGrid) {
const dx = gridPosition[0] - hoveredGrid[0]
const dz = gridPosition[1] - hoveredGrid[1]
const distance = Math.sqrt(dx * dx + dz * dz)
if (distance <= rippleRadius && distance > 0) {
const falloff = Math.max(0, 1 - distance / rippleRadius)
const rippleEffect = falloff * rippleScale
targetScale = 1 + rippleEffect * 3
}
}
const lerpFactor = 0.1
const newScale = currentScale + (targetScale - currentScale) * lerpFactor
setCurrentScale(newScale)
meshRef.current.scale.z = newScale
}
})
useEffect(() => {
if (meshRef.current) {
meshRef.current.userData.gridPosition = gridPosition
}
}, [gridPosition])
return (
<mesh
ref={meshRef}
geometry={geometry}
position={position}
rotation={[Math.PI / 2, 0, 0]}
>
<meshPhysicalMaterial
color={gridColor}
roughness={0.5}
metalness={1}
clearcoat={1}
clearcoatRoughness={0}
clearcoatNormalScale={1}
clearcoatNormalMap={null}
/>
</mesh>
)
}
function HoverDetector({
onHoverChange,
}: {
gridSize: number
spacingX: number
spacingZ: number
onHoverChange: (hoveredGrid: [number, number] | null) => void
}) {
const { camera, raycaster, pointer, scene } = useThree()
useFrame(() => {
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(scene.children, true)
if (intersects.length > 0) {
for (const intersect of intersects) {
const mesh = intersect.object
if (mesh.userData && mesh.userData.gridPosition) {
const gridPos = mesh.userData.gridPosition as [number, number]
onHoverChange(gridPos)
return
}
}
}
onHoverChange(null)
})
return null
}
interface GridOfGridesProps {
gridSize: number
gridWidth: number
gridLength: number
gap: number
rippleScale: number
rippleRadius: number
cornerRadius: number
}
function GridOfGrides({
gridSize,
gridWidth,
gridLength,
gap,
rippleScale,
rippleRadius,
cornerRadius,
}: GridOfGridesProps) {
const spacingX = gridWidth + gap
const spacingZ = gridLength + gap
const [hoveredGrid, setHoveredGrid] = useState<[number, number] | null>(null)
const Grides = []
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
const posX = (x - (gridSize - 1) / 2) * spacingX
const posZ = (z - (gridSize - 1) / 2) * spacingZ
Grides.push(
<Grid
key={`${x}-${z}`}
position={[posX, -0.85, posZ]}
width={gridWidth}
length={gridLength}
cornerRadius={cornerRadius}
gridPosition={[x, z]}
hoveredGrid={hoveredGrid}
rippleScale={rippleScale}
rippleRadius={rippleRadius}
/>
)
}
}
return (
<>
<HoverDetector
gridSize={gridSize}
spacingX={spacingX}
spacingZ={spacingZ}
onHoverChange={setHoveredGrid}
/>
{Grides}
</>
)
}
interface AnimatedGridPatternProps {
gridSize?: number
gridWidth?: number
gridLength?: number
gap?: number
rippleScale?: number
rippleRadius?: number
cornerRadius?: number
cameraPosition?: [number, number, number]
cameraRotation?: [number, number, number]
fov?: number
}
export function AnimatedGridPattern({
gridSize = 10,
gridWidth = 4,
gridLength = 4,
gap = 0.05,
rippleScale = 2.5,
rippleRadius = 2,
cornerRadius = 0.8,
cameraPosition = [-9.31, 12, 24.72],
cameraRotation = [-0.65, -0.2, -0.13],
fov = 35,
}: AnimatedGridPatternProps) {
return (
<div className="relative z-0 h-full w-full bg-gray-50 dark:bg-black">
<Canvas
camera={{
position: cameraPosition,
rotation: cameraRotation,
fov: fov,
}}
>
<ambientLight intensity={1} />
<directionalLight position={[10, 15, 10]} intensity={10} castShadow />
<directionalLight
position={[-10, 10, -5]}
intensity={10}
color="#ffffff"
/>
<directionalLight
position={[5, -10, 15]}
intensity={5}
color="#f0f8ff"
/>
<pointLight position={[0, 20, 3]} intensity={2} distance={50} />
<pointLight
position={[15, 5, 15]}
intensity={1.5}
distance={40}
color="#ffffff"
/>
<GridOfGrides
gridSize={gridSize}
gridWidth={gridWidth}
gridLength={gridLength}
gap={gap}
rippleScale={rippleScale}
rippleRadius={rippleRadius}
cornerRadius={cornerRadius}
/>
</Canvas>
</div>
)
}