BarsWave

PreviousNext

Vertical wave bars animate in symmetry, forming a subtle motion-driven background layer.

Docs
scrollxuicomponent

Preview

Loading preview…
components/ui/barswave.tsx
"use client";
import React from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";

interface BarsWaveProps {
  barCount?: number;
  minHeight?: number;
  maxHeight?: number;
  fillColor?: string;
  borderColor?: string;
  barOpacity?: number;
  animationDuration?: number;
  variant?: "filled" | "outlined";
  className?: string;
  children?: React.ReactNode;
}

export function BarsWave({
  barCount = 15,
  minHeight = 15,
  maxHeight = 85,
  fillColor,
  borderColor,
  barOpacity = 1,
  animationDuration = 3,
  variant = "outlined",
  className,
  children,
}: BarsWaveProps) {
  const halfCount = Math.ceil(barCount / 2);
  
  const barAnimations = React.useMemo(() => {
    return Array.from({ length: halfCount }, (_, i) => {
      const heights = [
        Math.random() * (maxHeight - minHeight) + minHeight,
        Math.random() * (maxHeight - minHeight) + minHeight,
        Math.random() * (maxHeight - minHeight) + minHeight,
      ];
      
      return {
        id: i,
        heights: [...heights, heights[0]],
        delay: Math.random() * 2,
        duration: animationDuration + Math.random() * 2,
      };
    });
  }, [halfCount, minHeight, maxHeight, animationDuration]);

  const allBars = React.useMemo(() => {
    const leftBars = barAnimations.map((bar, i) => ({
      ...bar,
      id: `left-${i}`,
      position: i,
    }));
    
    const rightBars = [...barAnimations].reverse().map((bar, i) => ({
      ...bar,
      id: `right-${i}`,
      position: halfCount + i,
    }));
    
    return [...leftBars, ...rightBars].slice(0, barCount);
  }, [barAnimations, halfCount, barCount]);

  return (
    <div className={cn("relative overflow-hidden", className)}>
      <div className="absolute inset-0 flex items-end justify-around gap-1 px-4">
        {allBars.map((bar) => (
          <motion.div
            key={bar.id}
            className={cn(
              "w-full max-w-[80px] rounded-t-md",
              variant === "filled" 
                ? "bg-gray-900/10 dark:bg-white/10" 
                : "bg-transparent border-2 border-gray-900/20 dark:border-white/20"
            )}
            style={{
              ...(fillColor && variant === "filled" && { backgroundColor: fillColor }),
              ...(borderColor && variant === "outlined" && { borderColor: borderColor }),
              opacity: barOpacity,
            }}
            initial={{ height: `${bar.heights[0]}%` }}
            animate={{
              height: bar.heights.map(h => `${h}%`),
            }}
            transition={{
              duration: bar.duration,
              delay: bar.delay,
              repeat: Infinity,
              ease: "easeInOut",
            }}
          />
        ))}
      </div>

      {children && <div className="relative z-10">{children}</div>}
    </div>
  );
}

Installation

npx shadcn@latest add @scrollxui/barswave

Usage

import { Barswave } from "@/components/barswave"
<Barswave />