npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dumogu-scrollbar

v1.0.3

Published

A simple custom scrollbar plugin

Readme

English | 中文 | 한국어 | 日本語

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.

DEMO

Install

npm install dumogu-scrollbar

Or 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.body

Element 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';