UI/Components/Loaders/Bar Loader

Bar Loader

A customizable bar loader component for Remotion.

A simple, animated loading bar with a text label.

Loading...
0:00 / 0:05

Installation

CLI

npx shadcn@latest add "https://clippkit.com/r/bar-loader"

Manual

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

"use client";
 
import React from "react";
import { useCurrentFrame, useVideoConfig } from "remotion";
 
interface BarLoaderProps {
  loadingText?: string;
  barColor?: string;
  textColor?: string;
  height?: string | number;
  width?: string | number;
  containerStyle?: React.CSSProperties;
  hideText?: boolean;
  barBorderRadius?: string | number;
  trackColor?: string;
  barHeight?: string | number;
}
 
export function BarLoader({
  loadingText = "Loading...",
  barColor = "var(--foreground)",
  textColor = "var(--foreground)",
  height: propHeight,
  width: propWidth,
  containerStyle,
  hideText = false,
  barBorderRadius,
  trackColor = "var(--muted)",
  barHeight: propBarHeight,
}: BarLoaderProps) {
  const frame = useCurrentFrame();
  const {
    durationInFrames,
    width: videoWidth,
    height: videoHeight,
  } = useVideoConfig();
 
  let computedWidth: number;
  if (typeof propWidth === "number") {
    computedWidth = propWidth;
  } else if (typeof propWidth === "string" && propWidth.endsWith("%")) {
    const percentage = parseFloat(propWidth.substring(0, propWidth.length - 1));
    computedWidth = !isNaN(percentage)
      ? (percentage / 100) * videoWidth
      : videoWidth;
  } else {
    computedWidth = videoWidth;
  }
 
  let computedHeight: number;
  if (typeof propHeight === "number") {
    computedHeight = propHeight;
  } else if (typeof propHeight === "string" && propHeight.endsWith("%")) {
    const percentage = parseFloat(
      propHeight.substring(0, propHeight.length - 1)
    );
    computedHeight = !isNaN(percentage)
      ? (percentage / 100) * videoHeight
      : videoHeight / 4; // Default to 1/4th of video height if not specified
  } else {
    computedHeight = videoHeight / 4; // Default to 1/4th of video height
  }
 
  const finalWidth = computedWidth;
  const finalHeight = computedHeight;
  const calculatedBarHeight = Math.max(8, finalHeight / 4); // Ensure bar is at least 8px or 1/4 of component height
 
  let actualBarHeight: number;
  if (typeof propBarHeight === "number") {
    actualBarHeight = propBarHeight;
  } else if (typeof propBarHeight === "string") {
    if (propBarHeight.endsWith("%")) {
      const percentage = parseFloat(
        propBarHeight.substring(0, propBarHeight.length - 1)
      );
      actualBarHeight = !isNaN(percentage)
        ? (percentage / 100) * finalHeight
        : calculatedBarHeight;
    } else if (propBarHeight.endsWith("px")) {
      const pxValue = parseFloat(
        propBarHeight.substring(0, propBarHeight.length - 2)
      );
      actualBarHeight = !isNaN(pxValue) ? pxValue : calculatedBarHeight;
    } else {
      // Assuming it's a number as a string, or fallback
      const numValue = parseFloat(propBarHeight);
      actualBarHeight = !isNaN(numValue) ? numValue : calculatedBarHeight;
    }
  } else {
    actualBarHeight = calculatedBarHeight;
  }
 
  const textHeight = finalHeight - actualBarHeight;
  const actualBarBorderRadius =
    barBorderRadius !== undefined ? barBorderRadius : actualBarHeight / 2;
 
  const progress = frame / durationInFrames;
 
  return (
    <div
      style={{
        width: finalWidth,
        height: finalHeight,
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        overflow: "hidden",
        backgroundColor: "transparent",
        textAlign: "center",
        ...containerStyle,
      }}
    >
      {!hideText && (
        <div
          style={{
            height: textHeight,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            color: textColor,
            fontSize: Math.max(16, textHeight / 2.5), // Responsive font size, adjusted for better fit
            fontWeight: "bold",
            lineHeight: 1.2, // Ensure text is vertically centered if it wraps
            paddingBottom: actualBarHeight / 4, // Add some space between text and bar
          }}
        >
          {loadingText}
        </div>
      )}
      <div
        style={{
          width: "80%", // Bar container width
          height: actualBarHeight,
          backgroundColor: trackColor, // Background of the bar track
          borderRadius: actualBarBorderRadius,
          overflow: "hidden",
          marginTop: hideText ? 0 : actualBarHeight / 4, // Adjust top margin if text is hidden
        }}
      >
        <div
          style={{
            width: `${progress * 100}%`,
            height: "100%",
            backgroundColor: barColor,
            borderRadius: actualBarBorderRadius,
            transition: "width 0.1s linear",
          }}
        />
      </div>
    </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 BarLoader 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.

Project Structure Example

Here’s an example folder layout showing where to place the component and how it fits into a typical Remotion project

app/main.tsx
import React from "react";
import { Player } from "@remotion/player";
 
import BarLoader from "../components/bar-loader";
 
// Define props for the new composition component
interface BarLoaderCompositionProps {
  barLoaderProps: React.ComponentProps<typeof BarLoader>;
}
 
// New component combining BarLoader
const BarLoaderComposition: React.FC<BarLoaderCompositionProps> = ({
  barLoaderProps,
}) => {
  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <BarLoader {...barLoaderProps} />
    </div>
  );
};
 
export function BarLoaderDemo() {
  const barLoaderProps = React.useMemo(
    () => ({
      loadingText: "Loading...",
      barColor: "var(--primary)", // Green bar
      textColor: "var(--primary)",
      width: "30%",
      height: "20%",
      hideText: false, // Uncomment to hide the loading text
      barBorderRadius: 4, // Example: 4px border radius for the bar
      trackColor: "var(--muted)", // Example: Darker track color
      barHeight: "10px", // Example: 20px bar height
    }),
    []
  );
 
  const playerDurationInFrames = 150; // 5 seconds at 30fps
 
  return (
    <Player
      component={BarLoaderComposition}
      inputProps={{ barLoaderProps }}
      durationInFrames={playerDurationInFrames}
      compositionWidth={640}
      compositionHeight={360}
      fps={30}
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "transparent",
      }}
      controls
      loop
    />
  );
}

Define a Composition

In your Remotion project's entry file (commonly src/Root.tsx, src/index.tsx, app/main.tsx), import BarLoader and define a Composition.

app/main.tsx (or equivalent)
import { Composition } from "remotion";

import BarLoader from "@/components/clippkit/bar-loader"; // Assuming you placed it in src/components/clippkit

export const MyComposition = () => {
  return (
    <Composition
      id="BarLoaderScene"
      component={BarLoader}
      durationInFrames={150} // Example: 5 seconds at 30fps
      fps={30}
      width={1280}
      height={720}
      defaultProps={{
        loadingText: "Generating Video...",
        barColor: "#1E90FF", // DodgerBlue
        textColor: "#333333",
        width: "80%",
        height: 100,
      }}
    />
  );
};

API Reference

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

PropTypeDefaultDescription
loadingTextstring"Loading..."The text displayed above the loading bar.
barColorstring"var(--foreground)"The color of the loading bar.
textColorstring"var(--foreground)"The color of the loading text.
widthstring | numberVideo config widthThe width of the component. Can be a number (pixels) or a string (e.g., "50%").
heightstring | numberVideo config height / 4The height of the component. Can be a number (pixels) or a string (e.g., "25%").
containerStyleReact.CSSProperties{}Custom styles for the main container div.
hideTextbooleanfalseControls the visibility of the loading text.
barBorderRadiusstring | numberbarHeight / 2The border radius of the progress bar and its track.
trackColorstring"var(--muted)"The color of the loading bar's track.
barHeightstring | numberCalculatedThe height of the progress bar. Can be a number (pixels), percentage, or pixel string.

On this page