Soft colored light orbs of different sizes drifting in and out of focus.
three @react-three/fiber @react-three/drei<BokehLights /> inside your own <Canvas>.import { useMemo, useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import type { Mesh } from 'three';
const PALETTE = ['#22e0ff', '#ff2fd0', '#8a5cff', '#ff8a3d', '#ff5db1'];
function rand(s: number) {
const x = Math.sin(s) * 43758.5453;
return x - Math.floor(x);
}
export default function BokehLights({ scale = 1 }: { color?: string; scale?: number }) {
const refs = useRef<(Mesh | null)[]>([]);
const lights = useMemo(
() =>
Array.from({ length: 16 }, (_, i) => ({
x: (rand(i * 1.3) - 0.5) * 5,
y: (rand(i * 2.7) - 0.5) * 3,
z: (rand(i * 4.1) - 0.5) * 2 - 0.5,
r: 0.15 + rand(i * 5.9) * 0.5,
c: PALETTE[Math.floor(rand(i * 7.3) * PALETTE.length) % PALETTE.length],
drift: 0.2 + rand(i * 9.1) * 0.3,
phase: rand(i * 3.3) * Math.PI * 2,
})),
[]
);
useFrame((state) => {
const t = state.clock.elapsedTime;
lights.forEach((l, i) => {
const m = refs.current[i];
if (!m) return;
m.position.x = l.x + Math.sin(t * l.drift + l.phase) * 0.4;
m.position.y = l.y + Math.cos(t * l.drift * 0.8 + l.phase) * 0.3;
});
});
return (
<group scale={scale}>
{lights.map((l, i) => (
<mesh key={i} ref={(el) => (refs.current[i] = el)} position={[l.x, l.y, l.z]}>
<circleGeometry args={[l.r, 32]} />
<meshBasicMaterial
color={l.c}
transparent
opacity={0.5}
blending={THREE.AdditiveBlending}
depthWrite={false}
toneMapped={false}
/>
</mesh>
))}
</group>
);
}