Scroll Progress Bar
The Scroll Progress Bar component enhances user experience by providing a visually dynamic indicator as the user scrolls through the page. It visually informs users how much of the page has been scrolled, making it especially useful for longer content. Unlike static progress bars, this animated indicator offers a more modern and interactive experience. The scroll bar can be configured as a circular or linear bar and can be positioned in specific corners of the page, such as the bottom-right or top-left. Built using Framer Motion and Tailwind CSS, the Scroll Progress Bar ensures smooth transitions and an aesthetically pleasing design.
Bar Style
Installation
Install the following dependencies:
npm i motion clsx tailwind-merge
Add util file
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the following code into your project.
"use client";
import React from "react";
import {
motion,
useMotionValueEvent,
useScroll,
useTransform,
} from "motion/react";
import { cn } from "@/lib/utils";
interface ScrollProgressBarType {
type?: "circle" | "bar";
position?: "top-right" | "bottom-right" | "top-left" | "bottom-left";
color?: string;
strokeSize?: number;
showPercentage?: boolean;
}
export default function ScrollProgressBar({
type = "circle",
position = "bottom-right",
color = "var(--primary)",
strokeSize = 2,
showPercentage = false,
}: ScrollProgressBarType) {
const { scrollYProgress } = useScroll();
const scrollPercentage = useTransform(scrollYProgress, [0, 1], [0, 100]);
const [percentage, setPercentage] = React.useState(0);
useMotionValueEvent(scrollPercentage, "change", (latest) => {
setPercentage(Math.round(latest));
});
if (type === "bar") {
return (
<div
className="fixed start-0 end-0 top-0 pointer-events-none"
style={{ height: `${strokeSize + 2}px` }}
>
<span
className="bg-primary h-full w-full block"
style={{
backgroundColor: color,
width: `${percentage}%`,
}}
></span>
</div>
);
}
return (
<div
className={cn("fixed flex items-center justify-center", {
"top-0 end-0": position === "top-right",
"bottom-0 end-0": position === "bottom-right",
"top-0 start-0": position === "top-left",
"bottom-0 start-0": position === "bottom-left",
})}
>
{percentage > 0 && (
<>
<svg width="100" height="100" viewBox="0 0 100 100">
<circle
cx="50"
cy="50"
r="30"
fill="none"
strokeWidth={strokeSize}
/>
<motion.circle
cx="50"
cy="50"
r="30"
pathLength="1"
stroke={color}
fill="none"
strokeDashoffset="0"
strokeWidth={strokeSize}
style={{ pathLength: scrollYProgress }}
/>
</svg>
{showPercentage && (
<span className="text-sm absolute ml-2">{percentage}%</span>
)}
</>
)}
</div>
);
}
API
Prop | Type | Default | Description |
---|---|---|---|
type | `"circle" | "bar"` | "circle" |
position | `"top-right" | "bottom-right" | "top-left" |
color | string | var(--primary) | The color of the progress indicator |
strokeSize | number | 2 | The stroke size of the circular progress bar or the height of the progress bar |
showPercentage | boolean | false | Whether to display the percentage value inside the progress indicator |