dumogu-scrollbar
v1.0.3
Published
A simple custom scrollbar plugin
Maintainers
Readme
Introduction
A simple and lightweight custom scrollbar plugin. The compressed file is less than 10KB.
The scrollbar is rendered as an absolutely-positioned overlay — it does not wrap or modify the scroll container in any way. By default it mounts to document.body, keeping your original DOM structure intact.
You can also use the DumoguScrollbarRail component directly to place scrollbar rails anywhere you like with fully custom styles.
Install
npm install dumogu-scrollbarOr load directly in the browser via UMD:
<link rel="stylesheet" href="dumogu-scrollbar/dist/dumogu-scrollbar.css" />
<script src="dumogu-scrollbar/dist/dumogu-scrollbar.umd.js"></script>
<script>
const { DumoguScrollbar, DumoguScrollbarRail } = window['dumogu-scrollbar'];
</script>Quick Start
1. Import CSS
With a bundler:
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';Or in the browser:
<link rel="stylesheet" href="dumogu-scrollbar/dist/dumogu-scrollbar.css" />2. Hide native scrollbars
The plugin does not hide native scrollbars for you. Add this CSS to the target element:
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}Important: Do NOT mount the custom scrollbar into the scrollable element itself. Since the scrollbar overlay uses position: absolute, placing it inside a scroll container would cause it to move with the scroll position. Instead, mount it to body (the default) or to a wrapper element outside the scroll container.
Usage
Page-level scrollbar (window)
Call bind() with no arguments to bind to window, and mount() with no arguments to mount to body:
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';
import { DumoguScrollbar } from 'dumogu-scrollbar';
const ds = new DumoguScrollbar({
keepShow: false,
stopClickPropagation: true,
});
ds.bind(); // bind to window
ds.mount(); // mount to document.bodyElement scrollbar
Pass a scrollable element to bind():
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';
import { DumoguScrollbar } from 'dumogu-scrollbar';
const ds = new DumoguScrollbar({
keepShow: true,
stopClickPropagation: true,
});
ds.bind(document.querySelector('#my-scroll-container'));
ds.mount();Mount to a custom container
Pass a container element to mount(). The scrollbar overlay will be positioned relative to that container instead of body:
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';
import { DumoguScrollbar } from 'dumogu-scrollbar';
const ds = new DumoguScrollbar({ keepShow: false });
ds.bind(document.querySelector('#my-scroll-container'));
ds.mount(document.querySelector('#my-wrapper'));This is useful when your scroll container is inside a positioned ancestor — mounting to body would cause misalignment, but mounting to the wrapper keeps the scrollbar positioned correctly without manual CSS.
Using DumoguScrollbarRail standalone
You can create individual rails (X or Y) and place them anywhere in the DOM — even completely outside the scroll container's layout:
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';
import { DumoguScrollbarRail } from 'dumogu-scrollbar';
// Always-visible horizontal rail
const railX = new DumoguScrollbarRail({
isX: true,
keepShow: true,
stopClickPropagation: true,
});
document.querySelector('#rail-container').appendChild(railX.railEl);
railX.bind(document.querySelector('#my-scroll-container'));
railX.railEl.classList.add('my-custom-rail-style');You can also create hover-only rails by setting keepShow: false:
// Only visible on hover or when hovering the scroll target
const railX = new DumoguScrollbarRail({
isX: true,
keepShow: false,
stopClickPropagation: true,
});Each rail is independent — you can mix always-show and hover-show rails in different positions.
Managing multiple instances
When you have several scroll containers on the page, use a shared requestAnimationFrame loop to keep all scrollbars updated:
import 'dumogu-scrollbar/dist/dumogu-scrollbar.css';
import { DumoguScrollbar } from 'dumogu-scrollbar';
const dsList = [];
const railList = [];
// Create instances...
const ds1 = new DumoguScrollbar({ keepShow: true });
ds1.bind(document.querySelector('#scroll-1'));
ds1.mount();
dsList.push(ds1);
function updateLoop() {
requestAnimationFrame(() => {
dsList.forEach(item => item.update());
railList.forEach(item => item.update());
updateLoop();
});
}
updateLoop();Each DumoguScrollbar instance manages both X and Y rails internally. Use DumoguScrollbarRail standalone when you need to position rails independently.
API
new DumoguScrollbar(options?)
| Option | Type | Default | Description |
|---|---|---|---|
| keepShow | boolean | false | Always show the scrollbar (otherwise shows on hover/drag) |
| stopClickPropagation | boolean | false | Stop click events from propagating on the rail |
new DumoguScrollbarRail(options)
| Option | Type | Default | Description |
|---|---|---|---|
| isX | boolean | false | Horizontal rail (true) or vertical (false) |
| keepShow | boolean | false | Always show the rail |
| stopClickPropagation | boolean | false | Stop click events from propagating on the rail |
Methods
| Method | Description |
|---|---|
| bind(targetEl?) | Bind to a scrollable element. Omit to bind to window. |
| mount(containerEl?) | Mount the scrollbar overlay. Omit to mount to document.body. |
| unmount() | Remove the scrollbar overlay from the DOM. |
| update() | Recalculate scrollbar position and size. Call this after the target's content changes size. |
| destroy() | Unmount, remove all listeners, and mark the instance as destroyed. |
Properties
| Property | Type | Description |
|---|---|---|
| scrollbarEl | HTMLElement | The root scrollbar element. Add CSS classes to it for custom styling. |
| railX | DumoguScrollbarRail | The horizontal rail instance. |
| railY | DumoguScrollbarRail | The vertical rail instance. |
| targetEl | HTMLElement \| undefined | The currently bound scroll target. |
| isMounted | boolean | Whether the scrollbar is currently mounted. |
| isDestroyed | boolean | Whether the instance has been destroyed. |
DumoguScrollbarRail properties
| Property | Type | Description |
|---|---|---|
| railEl | HTMLElement | The root rail element. |
| thumbEl | HTMLElement | The thumb/dragger element inside the rail. |
| railContainerEl | HTMLElement | The container element between the rail and thumb. |
| isX | boolean | Get/set horizontal mode. |
| keepShow | boolean | Get/set always-show mode. |
Keeping scrollbar position in sync
When content inside the scroll container changes (DOM mutations, images loading, viewport resize), the scrollbar position and thumb size need to be recalculated. Use requestAnimationFrame to keep it updated:
function updateLoop() {
requestAnimationFrame(() => {
dsInstance.update();
updateLoop();
});
}
updateLoop();For multiple instances, update them all in a single loop:
const instances = [ds1, ds2, ds3];
function updateLoop() {
requestAnimationFrame(() => {
instances.forEach(item => item.update());
updateLoop();
});
}
updateLoop();If your content is static, you only need to call update() after making changes (e.g. after adding/removing DOM elements).
Behavior details
keepShow
| Value | Behavior |
|---|---|
| true | Scrollbar rails are always visible. The .dumogu-scrollbar-rail_keep_show class is added. |
| false (default) | Rails appear when: the mouse hovers over the rail itself, OR the mouse hovers over the bound scroll target. The .dumogu-scrollbar-rail_hover and .dumogu-scrollbar-rail_target_hover state classes control visibility. |
stopClickPropagation
When true, click events on the rail (including track-jump clicks) will not bubble up to parent elements. Useful when the scrollbar is inside an element with its own click handlers.
Thumb minimum size
The thumb has a minimum size (similar to native scrollbars), so it remains grabbable even when the content is extremely large relative to the viewport.
Custom Styling
Add CSS classes to scrollbarEl (for DumoguScrollbar) or railEl (for DumoguScrollbarRail), then target the internal class names in your stylesheet:
ds.scrollbarEl.classList.add('my-theme');/* Custom horizontal rail */
.my-theme .dumogu-scrollbar-rail_x {
height: 10px;
padding: 1px;
background: rgba(0, 0, 255, 0.1);
border-radius: 999px;
box-shadow: inset 1px 1px 2px #0000001c;
box-sizing: border-box;
bottom: 6px;
width: calc(100% - 12px);
left: 6px;
}
.my-theme .dumogu-scrollbar-rail_thumb_x {
border-radius: 999px;
background: #ffffff;
box-shadow: 0 0 2px #000000;
}
/* Custom vertical rail — offset to the left of the scroll container */
.my-theme .dumogu-scrollbar-rail_y {
width: 8px;
right: initial;
left: -14px;
}
.my-theme .dumogu-scrollbar-rail_thumb_y {
border-radius: 999px;
background: #e14fad;
background-image: linear-gradient(to top, #e14fad 0%, #f9d423 100%);
}For DumoguScrollbarRail standalone rails, add the class directly to railEl:
railX.railEl.classList.add('my-rail-theme');.my-rail-theme .dumogu-scrollbar-rail_thumb_x {
background: #43e97b;
background-image: linear-gradient(to right, #43e97b 0%, #38f9d7 100%);
}Positioning notes
The default rail CSS positions rails at the edges of the target area. Since rails use absolute positioning, you can override top/right/bottom/left to move them anywhere relative to the mount container. Use !important when needed to override inline styles.
Internal CSS class names
| Class | Description |
|---|---|
| .dumogu-scrollbar | Root overlay element |
| .dumogu-scrollbar-target | Positioned target area matching the scroll container |
| .dumogu-scrollbar-rail | A single rail (track) |
| .dumogu-scrollbar-rail_x / .dumogu-scrollbar-rail_y | Direction-specific rail |
| .dumogu-scrollbar-rail_container | Inner container of a rail |
| .dumogu-scrollbar-rail_thumb | The draggable thumb |
| .dumogu-scrollbar-rail_thumb_x / .dumogu-scrollbar-rail_thumb_y | Direction-specific thumb |
State classes
| Class | Applied when |
|---|---|
| .dumogu-scrollbar-rail_hidden | Content fits without scrolling |
| .dumogu-scrollbar-rail_keep_show | keepShow is true |
| .dumogu-scrollbar-rail_hover | Mouse is over the rail |
| .dumogu-scrollbar-rail_target_hover | Mouse is over the scroll target |
| .dumogu-scrollbar-rail_dragging | Thumb is being dragged |
TypeScript
Type definitions are included. Both ScrollbarOption and RailOption types are exported:
import type { ScrollbarOption, RailOption } from 'dumogu-scrollbar';