← All elements

Spectrum

Abstract

A ring of neon bars rising and falling like an audio equalizer.

Usage
Install: three @react-three/fiber @react-three/drei
Drop <Spectrum /> inside your own <Canvas>.
Mood
Rhythmic, electric, alive. Sound made visible.
Colors
Bars graded cyan to violet to magenta around the ring, glowing under bloom.
Motion
Each bar pulses its height on a phase offset so a wave travels around the circle.
View component source
import { useMemo, useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import type { Mesh } from 'three';

const N = 44;
const RADIUS = 1.4;

export default function Spectrum({ scale = 1 }: { color?: string; scale?: number }) {
  const refs = useRef<(Mesh | null)[]>([]);
  const bars = useMemo(() => {
    const c1 = new THREE.Color('#22e0ff');
    const c2 = new THREE.Color('#8a5cff');
    const c3 = new THREE.Color('#ff2fd0');
    const tmp = new THREE.Color();
    return Array.from({ length: N }, (_, i) => {
      const a = (i / N) * Math.PI * 2;
      const t = i / (N - 1);
      if (t < 0.5) tmp.copy(c1).lerp(c2, t * 2);
      else tmp.copy(c2).lerp(c3, (t - 0.5) * 2);
      return { a, x: Math.cos(a) * RADIUS, z: Math.sin(a) * RADIUS, color: tmp.getStyle() };
    });
  }, []);

  useFrame((state) => {
    const t = state.clock.elapsedTime;
    bars.forEach((b, i) => {
      const m = refs.current[i];
      if (!m) return;
      const h = 0.3 + (Math.sin(t * 3 + b.a * 3) * 0.5 + 0.5) * 1.5;
      m.scale.y = h;
      m.position.y = 0;
    });
  });

  return (
    <group scale={scale} rotation={[0.5, 0, 0]}>
      {bars.map((b, i) => (
        <mesh key={i} ref={(el) => (refs.current[i] = el)} position={[b.x, 0, b.z]} rotation={[0, -b.a, 0]}>
          <boxGeometry args={[0.08, 1, 0.08]} />
          <meshBasicMaterial color={b.color} toneMapped={false} />
        </mesh>
      ))}
    </group>
  );
}