feat: scroll functionality

This commit is contained in:
TrèsAbhi committed 2026-02-01 12:31:51 -06:00
1 parent ddb869e767
commit f84f846e76
2 files changed
+60 -7

No files matched your search

@@ -1,6 +1,7 @@
.scroller {
display: flex;
flex-direction: column;
gap: var(--space-3);
.content {
overflow: auto;
@@ -15,11 +16,15 @@
}
.bar-wrapper {
opacity: 0;
padding: var(--space-1);
background-color: blue;
background-color: var(--gray-3);
transition: opacity var(--duration-brief);
border-radius: var(--max-size);
.bar-container {
background-color: red;
height: 10px;
position: relative;
@@ -29,7 +34,8 @@
min-width: min(100%, var(--space-6));
position: absolute;
background-color: green;
border-radius: var(--max-size);
background-color: var(--gray-7);
cursor: grab;
@@ -39,4 +45,11 @@
}
}
}
&:hover,
&:focus-within {
.bar-wrapper {
opacity: 1;
}
}
}
@@ -10,32 +10,72 @@ export function Scroller({ children, className, ...props }: ScrollerProps) {
const content = useRef<HTMLDivElement>(null!);
useEffect(() => {
function updateSize() {
bar.current.style.width = `${(content.current.clientWidth / content.current.scrollWidth) * 100}%`;
function updateBarSize() {
const visible = content.current.clientWidth;
const total = content.current.scrollWidth;
if (total <= 0) return;
bar.current.style.width = `${(visible / total) * 100}%`;
}
function syncBarToScroll() {
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`;
}
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;
syncBarToScroll();
}
function handlePointerUp() {
function handlePointerUp(event: PointerEvent) {
bar.current.releasePointerCapture(event.pointerId);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
updateSize();
updateBarSize();
syncBarToScroll();
bar.current.addEventListener("pointerdown", handlePointerDown);
content.current.addEventListener("scroll", syncBarToScroll);
window.addEventListener("resize", updateBarSize);
return () => {
bar.current.removeEventListener("pointerdown", handlePointerDown);
content.current.removeEventListener("scroll", syncBarToScroll);
window.removeEventListener("resize", updateBarSize);
};
}, []);