UI/Components/Audio waveforms/Circular Waveform

Circular Waveform

A customizable circular audio waveform visualization component.

Animate a circular waveform, often used for audio visualizations.

0:00 / 0:10

Installation

CLI

COMING SOON

Installation via npx shadcnui install clipkittt@circular-waveform isn't quite ready yet. For now, please follow the manual installation instructions.

Eventually users will be able to run the following command:

npx shadcn@latest add "https://clippkit/r/circular-waveform"

The GitHub task can be found here.

Manual

Create a new file, for example, at src/components/circular-waveform.tsx (or your preferred location) and paste the following code into it.

src/components/circular-waveform.tsx
"use client";

import React from "react";
import { random, useCurrentFrame, useVideoConfig } from "remotion";

interface CircularWaveformProps {
  barCount?: number;
  barWidth?: number; // Represents strokeWidth for lines in SVG
  barColor?: string;
  waveAmplitude?: number;
  waveSpeed?: number;
  randomness?: number;
  radius?: number; // Radius of the circle on which waveforms are drawn
  centerOffset?: { x?: number; y?: number }; // Offset the center of the waveform
  containerStyle?: React.CSSProperties;
  barStyle?: React.CSSProperties; // Style for individual SVG lines
  height?: string | number;
  width?: string | number;
}

export default function CircularWaveform({
  barCount = 60, // Increased for a smoother circle
  barWidth = 5, // Thinner bars for a cleaner look
  barColor = "white",
  waveAmplitude = 50,
  waveSpeed = 10,
  randomness = 30,
  radius = 100, // Default radius
  centerOffset = { x: 0, y: 0 },
  containerStyle,
  barStyle,
  height: propHeight,
  width: propWidth,
}: CircularWaveformProps) {
  const frame = useCurrentFrame();
  const { width: videoWidth, height: videoHeight } = useVideoConfig();

  const finalWidth = propWidth ?? videoWidth;
  const finalHeight = propHeight ?? videoHeight;

  // Calculate center based on final dimensions
  const centerX = (typeof finalWidth === 'number' ? finalWidth / 2 : parseFloat(String(finalWidth).replace('px','')) / 2) + (centerOffset.x ?? 0);
  const centerY = (typeof finalHeight === 'number' ? finalHeight / 2 : parseFloat(String(finalHeight).replace('px','')) / 2) + (centerOffset.y ?? 0);

  const bars = Array.from({ length: barCount }).map((_, i) => {
    const seed = i * 1000;
    const angle = (i / barCount) * 2 * Math.PI; // Distribute bars in a circle
    const dynamicHeight =
      Math.max(5, Math.abs(Math.sin(frame / waveSpeed + i / (barCount / (2 * Math.PI)))) * waveAmplitude + random(seed) * randomness);

    // Calculate start and end points for each bar (line)
    const x1 = centerX + radius * Math.cos(angle);
    const y1 = centerY + radius * Math.sin(angle);
    const x2 = centerX + (radius + dynamicHeight) * Math.cos(angle);
    const y2 = centerY + (radius + dynamicHeight) * Math.sin(angle);

    return {
      x1,
      y1,
      x2,
      y2,
    };
  });

  return (
    <div
      style={{
        width: finalWidth,
        height: finalHeight,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        overflow: "hidden",
        backgroundColor: "transparent", // Example, can be customized
        position: "relative", // For potential future absolute positioning of elements
        ...containerStyle,
      }}
    >
      <svg width="100%" height="100%" style={{ overflow: "visible" }}>
        {bars.map((bar, i) => (
          <line
            key={i}
            x1={bar.x1}
            y1={bar.y1}
            x2={bar.x2}
            y2={bar.y2}
            stroke={barColor}
            strokeWidth={barWidth}
            style={{
              transition: "all 0.05s ease-out", // Smooth transitions for height changes
              ...barStyle,
            }}
          />
        ))}
      </svg>
    </div>
  );
}
Update the import paths in your Remotion compositions if you placed the file in a different location than shown in the usage examples.

Usage

Once the CircularWaveform component is added to your project (either via CLI or Manually), you can integrate it into your Remotion project by importing it and defining a Composition.

Prerequisite

Ensure you have a Remotion project set up. If not, please refer to the Remotion documentation to get started.

Define a Composition:

In your Remotion project's entry file (commonly src/Root.tsx or src/index.ts), import CircularWaveform and define a Composition.

src/main.tsx (or equivalent)
import { Player } from "@remotion/player";

import CircularWaveform from "@/components/circular-waveform"; // Assuming you placed it in src/components

export default function MyVideoComposition() {
  const waveformProps = {
    barCount: 70,
    barColor: "#3498db",
    waveAmplitude: 40,
    waveSpeed: 12,
    radius: 80,
  };

  return (
    <Player
      component={CircularWaveform}
      inputProps={waveformProps}
      durationInFrames={300}
      compositionWidth={640}
      compositionHeight={360}
      fps={30}
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "#2c3e50",
      }}
      autoPlay
      controls
      loop
    />
  );
}

API Reference

The component exported as CircularWaveform (e.g., from apps/docs/registry/default/ui/circular-waveform.tsx or your project's component path) accepts the following props to customize its appearance and animation:

PropTypeDefault ValueDescription
barCountnumber60Number of bars (lines) in the waveform.
barWidthnumber5Width (strokeWidth) of each bar in pixels.
barColorstring"white"Color of the bars. Accepts any valid CSS color value.
waveAmplitudenumber50Maximum length extension of the wave bars from the base radius.
waveSpeednumber10Speed of the wave animation. Higher value means slower animation.
randomnessnumber30Amount of random length variation applied to each bar. Set to 0 for no randomness.
radiusnumber100Radius of the base circle from which waveform bars extend.
centerOffset{ x?: number; y?: number }{ x: 0, y: 0 }Offsets the center of the circular waveform from the center of its container. Useful for precise positioning.
heightstring | numberVideo config heightExplicit height for the waveform container. Overrides video config height.
widthstring | numberVideo config widthExplicit width for the waveform container. Overrides video config width.
containerStyleReact.CSSProperties{}Custom CSS styles for the main container div.
barStyleReact.CSSProperties{}Custom CSS styles for individual bars (SVG <line> elements). Applied to each bar.

On this page