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
Install the following dependencies:
npm i framer-motion clsx tailwind-merge
Add util file
import { ClassValue, clsx } 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 "framer-motion";
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 = "hsl(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>
);
}
Usage
import ScrollProgressBar from "@/components/ui/scroll-progress-bar";
export default function ScrollProgressBarExample() {
return <ScrollProgressBar type="bar" />;
}
API
Prop | Type | Default | Description |
---|---|---|---|
type | "circle" | "bar" | "circle" | Determines the style of the progress bar: a circular progress bar or a bar. |
position | "top-right" | "bottom-right" | "top-left" | "bottom-left" | "bottom-right" | Sets the position of the progress indicator on the screen. |
color | string | "hsl(var(--primary))" | The color of the progress bar or circle stroke. |
strokeSize | number | 2 | The stroke size for the circular progress bar or height for the bar. |
showPercentage | boolean | false | Determines if the percentage value should be displayed within the circle. |