overlay-scrollbar
v0.1.6
Published
Dependency-free custom scrollbar (drag, touch, auto-hide) — modern replacement for slim-scroll
Maintainers
Readme
overlay-scrollbar
TypeScript, ~3 KB gzipped, Drag-to-scroll, Touch (nativ), Auto-Hide, programmatisches Scrollen.
Installation
npm install overlay-scrollbarVerwendung
import { OverlayScrollbar } from "overlay-scrollbar";
const instance = new OverlayScrollbar("#my-list", {
width: 8,
color: "#8f8f8f",
alwaysVisible: false,
position: "right", // oder "left"
});
instance.scrollTo(0, "smooth");
instance.refresh(); // nach externen DOM-Änderungen, die die Observer nicht mitbekommen
instance.destroy(); // Element in Originalzustand zurückversetzenFür mehrere Elemente auf einmal (analog zum alten $('.el').slimScroll()):
import { overlayScrollbar } from "overlay-scrollbar";
overlayScrollbar(".scrollable", { alwaysVisible: true });Optionen
| Option | Default | Beschreibung |
| --- | --- | --- |
| width | 8 | Breite des Thumbs in px |
| color | #8f8f8f | Thumb-Farbe |
| opacity | 0.85 | Thumb-Opazität, während sichtbar |
| alwaysVisible | false | Nie ausblenden |
| distance | 2 | Abstand zum Rand in px |
| railVisible | true | Hintergrund-Rail anzeigen |
| railColor / railOpacity | #000000 / 0.06 | Rail-Farbe/-Opazität |
| minBarHeight | 24 | Mindesthöhe des Thumbs in px |
| fadeDelay / fadeDuration | 900 / 200 | Ausblend-Verzögerung/-Dauer in ms |
| borderRadius | 6 | Thumb-Eckenradius in px |
| position | "right" | "left" oder "right" |
| allowPageScroll | false | Scroll-Chaining zur Seite erlauben |
| onScroll | – | Callback { scrollTop, scrollHeight, clientHeight } |
Funktionsweise: Das Zielelement (el) behält seine Position und Größe im Layout (Flex/Grid/%/vh — was auch immer deine CSS vorgibt) exakt wie zuvor; nur sein Inhalt wandert in einen inneren, nativ scrollbaren Viewport-Container, dessen native Scrollbar optisch versteckt wird. Ein Thumb/Rail-Overlay darin spiegelt Scroll-Position und -Größe.
React / Vue / Angular / andere Frameworks mit eigenem DOM-Reconciler
new OverlayScrollbar(el) verschiebt die Kind-Elemente von el in einen selbst erzeugten Viewport-Container. Das funktioniert nicht zuverlässig, wenn ein Framework diese Kinder selbst verwaltet (z. B. React bei bedingtem Rendering, Routen-Wechseln, Listen mit wechselnden Keys) — das Framework versucht dann irgendwann, einen Knoten aus dem Elternteil zu entfernen, den es zu kennen glaubt, der aber tatsächlich woanders (im injizierten Viewport) sitzt. Das äußert sich z. B. bei React als:
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.Für solche Fälle: viewport/rail/bar selbst als JSX rendern und der Library per Ref übergeben, statt sie automatisch bauen zu lassen — dann fasst die Library keine vom Framework verwalteten Kind-Knoten an.
import { useEffect, useRef } from "react";
import { OverlayScrollbar } from "overlay-scrollbar";
function ScrollableList({ children }: { children: React.ReactNode }) {
const hostRef = useRef<HTMLDivElement>(null);
const viewportRef = useRef<HTMLDivElement>(null);
const railRef = useRef<HTMLDivElement>(null);
const barRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!hostRef.current || !viewportRef.current || !railRef.current || !barRef.current) return;
const scrollbar = new OverlayScrollbar({
host: hostRef.current,
viewport: viewportRef.current,
rail: railRef.current,
bar: barRef.current,
});
return () => scrollbar.destroy();
}, []);
return (
<div ref={hostRef} style={{ position: "relative", height: "100%" }}>
<div ref={viewportRef}>{children}</div>
<div ref={railRef} />
<div ref={barRef} />
</div>
);
}host braucht selbst keine overflow/position-Angabe — die Library setzt beides. viewport/rail/bar brauchen keine eigenen Styles, die Library ergänzt ihre CSS-Klassen automatisch.
Lizenz
MIT
