Files
patchy631--ai-engineering-hub/open-agent-builder/components/ui/scrollbar.tsx
T
2026-07-13 12:37:47 +08:00

121 lines
3.2 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
const padding = 8;
function Scrollbar() {
const ref = useRef<HTMLDivElement>(null);
const isDragging = useRef(false);
const startY = useRef(0);
const startScrollY = useRef(0);
useEffect(() => {
const thumbElement = ref.current!.children[0] as HTMLDivElement;
let thumbHeight = 0;
const updateBounds = () => {
const maxScrollY = document.body.scrollHeight - window.innerHeight;
thumbHeight = window.innerHeight / document.body.scrollHeight;
const scrollPosition =
padding +
(window.scrollY / maxScrollY) *
(window.innerHeight - thumbHeight * window.innerHeight - padding * 2);
Object.assign(thumbElement.style, {
height: `${thumbHeight * 100}%`,
transform: `translateY(${scrollPosition}px)`,
});
};
updateBounds();
const onMouseMove = (e: MouseEvent) => {
if (!isDragging.current) return;
const deltaY = e.clientY - startY.current;
const maxScrollY = document.body.scrollHeight - window.innerHeight;
const scrollRatio =
deltaY / (window.innerHeight - thumbHeight * window.innerHeight);
window.scrollTo({
top: startScrollY.current + scrollRatio * maxScrollY,
behavior: "instant",
});
showScrollbar();
};
let scrollbarHideTimeout: number = 0;
const showScrollbar = () => {
if (thumbHeight === 1 || window.innerWidth < 996) return;
clearTimeout(scrollbarHideTimeout);
thumbElement.classList.remove("opacity-0");
scrollbarHideTimeout = window.setTimeout(() => {
thumbElement.classList.add("opacity-0");
}, 1000);
};
const onMouseUp = () => {
isDragging.current = false;
document.body.style.userSelect = "";
thumbElement.classList.remove("!bg-black/40", "!w-8", "!right-6");
showScrollbar();
};
const onMouseDown = (e: MouseEvent) => {
isDragging.current = true;
startY.current = e.clientY;
startScrollY.current = window.scrollY;
document.body.style.userSelect = "none";
thumbElement.classList.add("!bg-black/40", "!w-8", "!right-6");
showScrollbar();
};
const onScroll = () => {
updateBounds();
showScrollbar();
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
window.addEventListener("scroll", onScroll, { passive: true });
thumbElement.addEventListener("mousedown", onMouseDown);
const container = ref.current!;
container.addEventListener("mouseenter", showScrollbar);
return () => {
window.removeEventListener("scroll", onScroll);
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousedown", onMouseDown);
container.removeEventListener("mouseenter", showScrollbar);
};
}, []);
return (
<div className="fixed right-0 w-16 z-[1000] h-screen top-0" ref={ref}>
<div className="bg-black/10 opacity-0 backdrop-blur-4 hover:w-6 w-4 right-8 hover:right-7 transition-[width,right,opacity] duration-[200ms] hover:bg-black/20 absolute top-0 rounded-full" />
</div>
);
}
export default Scrollbar;