feat: simplify scrollbar

This commit is contained in:
TrèsAbhi committed 2026-04-20 21:01:54 -05:00
1 parent 33209a6d31
commit 2e208507c3
2 files changed
+5 -107

No files matched your search

@@ -8,43 +8,13 @@
.content {
overflow: auto;
scrollbar-width: none;
padding-bottom: var(--space-4);
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
transition: scrollbar-color var(--duration-brief);
scrollbar-color: transparent transparent;
.bar-wrapper {
opacity: 0;
padding: var(--space-1);
background-color: var(--gray-3);
transition: opacity var(--duration-brief);
border-radius: var(--max-size);
.bar-container {
height: var(--space-3);
position: relative;
.bar {
height: 100%;
min-width: min(100%, var(--space-6));
position: absolute;
border-radius: var(--max-size);
background-color: var(--gray-7);
cursor: grab;
&:active {
cursor: grabbing;
}
}
&:hover {
scrollbar-color: var(--gray-7) var(--gray-3);
}
}
@@ -6,36 +6,11 @@ import styles from "./index.module.css";
interface ScrollerProps extends ComponentProps<"div"> {}
export function Scroller({ children, className, ...props }: ScrollerProps) {
const bar = useRef<HTMLDivElement>(null!);
const container = useRef<HTMLDivElement>(null!);
const content = useRef<HTMLDivElement>(null!);
const curtainLeft = useRef<HTMLDivElement>(null!);
const curtainRight = useRef<HTMLDivElement>(null!);
useEffect(() => {
function updateBarSize() {
const visible = content.current.clientWidth;
const total = content.current.scrollWidth;
if (total <= 0) return;
bar.current.style.width = `${(visible / total) * 100}%`;
}
function syncBar() {
const maxTravel = container.current.clientWidth - bar.current.clientWidth;
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
if (maxScroll <= 0 || maxTravel <= 0) {
bar.current.style.left = "0";
return;
}
const ratio = content.current.scrollLeft / maxScroll;
bar.current.style.left = `${ratio * maxTravel}px`;
}
function syncCurtains() {
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
@@ -61,61 +36,20 @@ export function Scroller({ children, className, ...props }: ScrollerProps) {
curtainRight.current.style.opacity = `${rightOpacity}`;
}
let lastX = 0;
function handlePointerDown(event: PointerEvent) {
lastX = event.clientX;
bar.current.setPointerCapture(event.pointerId);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const dx = event.clientX - lastX;
lastX = event.clientX;
const maxTravel = container.current.clientWidth - bar.current.clientWidth;
if (maxTravel <= 0) return;
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
const delta = (dx / maxTravel) * maxScroll;
content.current.scrollLeft += delta;
syncBar();
}
function handlePointerUp(event: PointerEvent) {
bar.current.releasePointerCapture(event.pointerId);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
function handleScroll() {
syncBar();
syncCurtains();
}
function handleResize() {
updateBarSize();
syncBar();
syncCurtains();
}
updateBarSize();
syncBar();
syncCurtains();
bar.current.addEventListener("pointerdown", handlePointerDown);
content.current.addEventListener("scroll", syncBar);
window.addEventListener("resize", handleResize);
content.current.addEventListener("scroll", handleScroll);
return () => {
bar.current.removeEventListener("pointerdown", handlePointerDown);
content.current.removeEventListener("scroll", syncBar);
window.removeEventListener("resize", handleResize);
content.current.removeEventListener("scroll", handleScroll);
};
@@ -127,12 +61,6 @@ export function Scroller({ children, className, ...props }: ScrollerProps) {
{children}
</div>
<div className={styles["bar-wrapper"]}>
<div className={styles["bar-container"]} ref={container}>
<div className={styles.bar} ref={bar} />
</div>
</div>
<div ref={curtainLeft} data-side="left" className={styles.curtain} />
<div ref={curtainRight} data-side="right" className={styles.curtain} />
</div>