OverlayScrollbars
OverlayScrollbars is a javascript scrollbar plugin that hides native scrollbars, provides custom styleable overlay scrollbars and keeps the native functionality and feeling.
Why
I created this plugin because I hate ugly and space consuming scrollbars. Similar plugins haven't met my requirements in terms of features, quality, simplicity, license or browser support.
Goals & Features
- Simple, powerful and well documented API
- High browser compatibility - Firefox 59+, Chrome 55+, Opera 42+, Edge 12+, Safari 10+ and IE 11
- Can be run on the server - SSR, SSG and ISR support
- Tested on various devices - Mobile, Desktop and Tablet
- Tested with various (and mixed) inputs - Mouse, Touch and Pen
- Treeshaking - bundle only what you really need
- Automatic update detection - no polling
- Usage of latest browser features - best performance in new browsers
- Bidirectional - LTR or RTL direction support
- Supports usage on the
body
element - Supports all virtual scrolling libraries
- Simple and effective scrollbar styling
- Highly customizable
- TypeScript support - fully written in TypeScript
- Dependency free - 100% self written to ensure small size and best functionality
- High quality and fully typed framework versions for
react
, vue
, angular
, svelte
and solid
.
Choose your framework
Additionally to the vanilla JavaScript version you can use the official framework components & utilities:
Getting started
npm & nodejs
OverlayScrollbars can be downloaded from npm or the package manager of your choice:
npm install overlayscrollbars
After installation it can be imported:
import 'overlayscrollbars/overlayscrollbars.css';
import {
OverlayScrollbars,
ScrollbarsHidingPlugin,
SizeObserverPlugin,
ClickScrollPlugin
} from 'overlayscrollbars';
Note: If the path 'overlayscrollbars/overlayscrollbars.css'
is not working use 'overlayscrollbars/styles/overlayscrollbars.css'
as the import path for the CSS file.
Manual download & embedding
You can use OverlayScrollbars without any bundler or package manager.
Simply download it from the Releases or use a CDN.
- Use the javascript files with the
.browser
extension. - Use the javascript files with the
.es5
extension if you need to support older browsers like IE11, otherwise use the .es6
files. - For production use the javascript / stylesheet files with the
.min
extension.
Embedd OverlayScrollbars manually in your HTML:
<link type="text/css" href="path/to/overlayscrollbars.css" rel="stylesheet" />
<script type="text/javascript" src="path/to/overlayscrollbars.browser.es.js" defer></script>
You can then use the global variable OverlayScrollbarsGlobal
to access the api similar to how you can do it in nodejs / modules:
var {
OverlayScrollbars,
ScrollbarsHidingPlugin,
SizeObserverPlugin,
ClickScrollPlugin
} = OverlayScrollbarsGlobal;
The examples in this documentation will use the import
syntax instead of the OverlayScrollbarsGlobal
object. Both versions are equivalent though.
Initialization
Note: During initialization its expected that the CSS file is loaded and parsed by the browser.
You can initialize either directly with an Element
or with an Object
where you have more control over the initialization process.
const osInstance = OverlayScrollbars(document.querySelector('#myElement'), {});
Bridging initialization flickering
If you initialize OverlayScrollbars it needs a few milliseconds to create and append all the elements to the DOM.
While this period the native scrollbars are still visible and are switched out after the initialization is finished. This is perceived as flickering.
To fix this behavior apply the data-overlayscrollbars-initialize
attribute to the target element (and html
element if the target element is body
).
<html data-overlayscrollbars-initialize>
<head></head>
<body data-overlayscrollbars-initialize></body>
</html>
<div data-overlayscrollbars-initialize>
OverlayScrollbars is applied to this div
</div>
Initialization with an Object
This is a in depth topic. Click here to read it.
The only required field is the target
field. This is the field to which the plugin is applied to.
If you use the object initialization only with the target
field, the outcome is equivalent to the element initialization:
OverlayScrollbars(document.querySelector('#myElement'), {});
OverlayScrollbars({ target: document.querySelector('#myElement') }, {});
In the initialization object you can specify how the library is handling generated elements.
For example you can appoint an existing element as the viewport
element. Like this the library won't generate it but take the specified element instead:
OverlayScrollbars({
target: document.querySelector('#target'),
elements: {
viewport: document.querySelector('#viewport'),
},
}, {});
This is very useful if you have a fixed DOM structure and don't want OverlayScrollbars to generate its own elements. Those cases arise very often when you want an other library to work together with OverlayScrollbars.
You can also decide to which element the scrollbars should be applied to:
OverlayScrollbars({
target: document.querySelector('#target'),
scrollbars: {
slot: document.querySelector('#target').parentElement,
},
}, {});
And last but not least you can decide when the initialization should be canceled:
OverlayScrollbars({
target: document.querySelector('#target'),
cancel: {
nativeScrollbarsOverlaid: true,
body: null,
}
}, {});
In the above example the initialization is canceled when the native scrollbars are overlaid or when your target is a body
element and the plugin determined that a initialization to the body
element would affect native functionality like window.scrollTo
.
Options
You can initialize OverlayScrollbars with an initial set of options, which can be changed at any time with the options
method:
OverlayScrollbars(document.querySelector('#myElement'), {
overflow: {
x: 'hidden',
},
});
Options in depth
This is a in depth topic. Click here to read it.
The default options are:
const defaultOptions = {
paddingAbsolute: false,
showNativeOverlaidScrollbars: false,
update: {
elementEvents: [['img', 'load']],
debounce: [0, 33],
attributes: null,
ignoreMutation: null,
},
overflow: {
x: 'scroll',
y: 'scroll',
},
scrollbars: {
theme: 'os-theme-dark',
visibility: 'auto',
autoHide: 'never',
autoHideDelay: 1300,
autoHideSuspend: false,
dragScroll: true,
clickScroll: false,
pointers: ['mouse', 'touch', 'pen'],
},
};
paddingAbsolute
Indicates whether the padding for the content shall be absolute.
showNativeOverlaidScrollbars
Indicates whether the native overlaid scrollbars shall be visible.
update.elementEvents
type | default |
---|
Array<[string, string]> | null | [['img', 'load']] |
An array of tuples. The first value in the tuple is an selector
and the second value are event names
. The plugin will update itself if any of the elements with the specified selector will emit any specified event. The default value can be interpreted as "The plugin will update itself if any img
element emits an load
event."
update.debounce
type | default |
---|
[number, number] | number | null | [0, 33] |
Note: If 0 is used for the timeout, requestAnimationFrame
instead of setTimeout
is used for the debounce.
Debounces the MutationObserver
which tracks changes to the content. If a tuple is passed, the first value is the timeout and second is the max wait. If only a number is passed you specify only the timeout and there is no max wait. With null there is no debounce. Usefull to fine-tune performance.
update.attributes
type | default |
---|
string[] | null | null |
Note: There is a base array of attributes that the MutationObserver
always observes, even if this option is null
.
An array of additional attributes that the MutationObserver
should observe for the content.
update.ignoreMutation
type | default |
---|
((mutation) => any) | null | null |
A function which receives a MutationRecord
as an argument. If the function returns a truthy value the mutation will be ignored and the plugin won't update. Usefull to fine-tune performance.
overflow.x
type | default |
---|
string | 'scroll' |
Note: Valid values are: 'hidden'
, 'scroll'
, 'visible'
, 'visible-hidden'
and 'visible-scroll'
.
The overflow behavior for the horizontal (x) axis.
overflow.y
type | default |
---|
string | 'scroll' |
Note: Valid values are: 'hidden'
, 'scroll'
, 'visible'
, 'visible-hidden'
and 'visible-scroll'
.
The overflow behavior for the vertical (y) axis.
scrollbars.theme
type | default |
---|
string | null | 'os-theme-dark' |
Applies the specified theme (classname) to the scrollbars.
scrollbars.visibility
Note: Valid values are: 'visible'
, 'hidden'
, and 'auto'
.
The base visibility of the scrollbars.
scrollbars.autoHide
Note: Valid values are: 'never'
, 'scroll'
, 'leave'
and 'move'
.
The possibility to hide visible scrollbars automatically after a certain user action.
scrollbars.autoHideDelay
The delay in milliseconds before the scrollbars are hidden automatically.
scrollbars.autoHideSuspend
Suspend the autoHide functionality until the first scroll interaction was performed.
The default value for this option is false
for backwards compatibility reasons but is recommended to be true
for better accessibility.
scrollbars.dragScroll
Indicates whether you can drag the scrollbar handles for scrolling.
scrollbars.clickScroll
Note: This options requires the ClickScrollPlugin to work.
Indicates whether you can click on the scrollbar track for scrolling.
scrollbars.pointers
type | default |
---|
string[] | null | ['mouse', 'touch', 'pen'] |
The PointerTypes
the plugin should react to.
TypeScript
export type Options = {
paddingAbsolute: boolean;
showNativeOverlaidScrollbars: boolean;
update: {
elementEvents: Array<[elementSelector: string, eventNames: string]> | null;
debounce: [timeout: number, maxWait: number] | number | null;
attributes: string[] | null;
ignoreMutation: ((mutation: MutationRecord) => any) | null;
};
overflow: {
x: OverflowBehavior;
y: OverflowBehavior;
};
scrollbars: {
theme: string | null;
visibility: ScrollbarsVisibilityBehavior;
autoHide: ScrollbarsAutoHideBehavior;
autoHideDelay: number;
autoHideSuspend: boolean;
dragScroll: boolean;
clickScroll: boolean;
pointers: string[] | null;
};
};
Events
You can initialize OverlayScrollbars with an initial set of events, which can be managed at any time with the on
and off
methods:
OverlayScrollbars(document.querySelector('#myElement'), {}, {
updated(osInstance, onUpdatedArgs) {
}
});
Events in depth
This is a in depth topic. Click here to read it.
Note: Every event receives the instance
from which it was dispatched as the first argument. Always.
initialized
arguments | description |
---|
instance | The instance which dispatched the event. |
Is dispatched after all generated elements, observers and events were appended to the DOM.
updated
arguments | description |
---|
instance | The instance which dispatched the event. |
onUpdatedArgs | An object which describes the update in detail. |
Note: If an update was triggered but nothing changed, the event won't be dispatched.
Is dispatched after the instace was updated.
destroyed
arguments | description |
---|
instance | The instance which dispatched the event. |
canceled | An boolean which indicates whether the initialization was canceled and thus destroyed. |
Is dispatched after all generated elements, observers and events were removed from the DOM.
scroll
arguments | description |
---|
instance | The instance which dispatched the event. |
event | The original event argument of the DOM event. |
Is dispatched by scrolling the viewport.
TypeScript
export type EventListenerArgs = {
initialized: [instance: OverlayScrollbars];
updated: [instance: OverlayScrollbars, onUpdatedArgs: OnUpdatedEventListenerArgs];
destroyed: [instance: OverlayScrollbars, canceled: boolean];
scroll: [instance: OverlayScrollbars, event: Event];
};
export interface OnUpdatedEventListenerArgs {
updateHints: {
sizeChanged: boolean;
directionChanged: boolean;
heightIntrinsicChanged: boolean;
overflowEdgeChanged: boolean;
overflowAmountChanged: boolean;
overflowStyleChanged: boolean;
hostMutation: boolean;
contentMutation: boolean;
};
changedOptions: PartialOptions;
force: boolean;
}
Instance
The OverlayScrollbars instance is created by calling the OverlayScrollbars
function with an element and options object.
const osInstance = OverlayScrollbars(document.body, {});
Instance Methods
This is a in depth topic. Click here to read it.
options(): Options
Get the current options of the instance.
returns | description |
---|
Options | The current options. |
options(newOptions, pure?): Options
Sets the current options of the instance.
parameter | type | description |
---|
newOptions | PartialOptions | The new (partial) options which should be applied. |
pure | boolean / undefined | Whether the options should be reset before the new options are added. |
returns | description |
---|
Options | The complete new options. |
on(eventListeners, pure?): Function
Adds event listeners to the instance.
parameter | type | description |
---|
eventListeners | EventListeners | An object which contains the added listeners. The fields are the event names and the values the listeners. |
pure | boolean / undefined | Whether all already added event listeners should be removed before the new listeners are added. |
returns | description |
---|
Function | A function which removes all added event listeners. |
on(name, listener): Function
Adds a single event listener to the instance.
parameter | type | description |
---|
name | string | The event name. |
listener | Function | The function which is invoked when the event is dispatched. |
returns | description |
---|
Function | A function which removes the added event listener. |
on(name, listeners): Function
Adds multiple event listeners to the instance.
parameter | type | description |
---|
name | string | The event name. |
listeners | Function[] | The functions which are invoked when the event is dispatched. |
returns | description |
---|
Function | A function which removes the added event listeners. |
off(name, listener): void
Removes a single event listener from the instance.
parameter | type | description |
---|
name | string | The event name. |
listener | Function | The function to be removed. |
off(name, listeners): void
Removes multiple event listeners from the instance.
parameter | type | description |
---|
name | string | The event name. |
listeners | Function[] | The functions to be removed. |
update(force?): boolean
Updates the instance.
parameter | type | description |
---|
force | boolean / undefined | Whether the update should force the cache to be invalidated. |
returns | description |
---|
Function | A boolean which indicates whether the update event was triggered through this update. |
state(): State
Gets the instances state.
returns | description |
---|
State | An object which describes the state of the instance. |
elements(): Elements
Gets the instances elments.
returns | description |
---|
Elements | An object which describes the elements of the instance. |
destroy(): void
Destroys the instance and removes all added elements.
plugin(plugin: object): object | undefined
Gets the instance modules instance of the passed plugin.
returns | description |
---|
object / undefined | An object which describes the plugins instance modules instance or undefined if no instance was found. |
TypeScript
interface OverlayScrollbars {
options(): Options;
options(newOptions: PartialOptions, pure?: boolean): Options;
on(eventListeners: EventListeners, pure?: boolean): () => void;
on<N extends keyof EventListenerArgs>(name: N, listener: EventListener<N>): () => void;
on<N extends keyof EventListenerArgs>(name: N, listener: EventListener<N>[]): () => void;
off<N extends keyof EventListenerArgs>(name: N, listener: EventListener<N>): void;
off<N extends keyof EventListenerArgs>(name: N, listener: EventListener<N>[]): void;
update(force?: boolean): boolean;
state(): State;
elements(): Elements;
destroy(): void;
plugin<P extends InstancePlugin>(osPlugin: P): InferInstancePluginModuleInstance<P> | undefined;
}
Static Object
The static OverlayScrollbars
object.
OverlayScrollbars.plugin(SomePlugin);
Static Object Methods
This is a in depth topic. Click here to read it.
plugin(plugin): object | undefined
Adds a single plugin.
parameter | type | description |
---|
plugin | object | The plugin to be added. |
returns | description |
---|
object / void | An object which describes the plugins static modules instance or void if no instance was found. |
plugin(plugins): (object | void)[]
Adds multiple plugins.
parameter | type | description |
---|
plugins | object[] | The plugins to be added. |
returns | description |
---|
(object / void)[] | An array which describes the plugins static modules instances or undefined if no instance was found. |
valid(osInstance): boolean
Checks whether the passed value is a valid and not destroyed overlayscrollbars instance
parameter | type | description |
---|
osInstance | any | The value to be checked. |
returns | description |
---|
boolean | Whether the passed value is a valid and not destroyed overlayscrollbars instance. |
env(): Environment
Gets the environment.
returns | description |
---|
Environment | An object which described the environment. |
TypeScript
interface OverlayScrollbarsStatic {
(target: InitializationTarget): OverlayScrollbars | undefined;
(target: InitializationTarget, options: PartialOptions, eventListeners?: EventListeners): OverlayScrollbars;
plugin(plugin: Plugin): InferStaticPluginModuleInstance<Plugin>
plugin(plugins: Plugin[]): InferStaticPluginModuleInstance<Plugin>[];
valid(osInstance: any): osInstance is OverlayScrollbars;
env(): Environment;
}
Styling
OverlayScrollbars comes with two themes called os-theme-dark
and os-theme-light
. You can use the scrollbars.theme
option to change the theme.
Custom themes can be done in multiple ways. The easiest and fastest is to use the predefined set of CSS Custom Properties
aka. CSS variables. In case those aren't enought you can add custom class names or add custom styling to the existing class names.
Styling in depth
This is a in depth topic. Click here to read it.
CSS Custom properties
OverlayScrollbars provides a set of CSS Custom Properties
which makes scrollbar styling easy and fast:
.os-scrollbar {
--os-size: 0;
--os-padding-perpendicular: 0;
--os-padding-axis: 0;
--os-track-border-radius: 0;
--os-track-bg: none;
--os-track-bg-hover: none;
--os-track-bg-active: none;
--os-track-border: none;
--os-track-border-hover: none;
--os-track-border-active: none;
--os-handle-border-radius: 0;
--os-handle-bg: none;
--os-handle-bg-hover: none;
--os-handle-bg-active: none;
--os-handle-border: none;
--os-handle-border-hover: none;
--os-handle-border-active: none;
--os-handle-min-size: 33px;
--os-handle-max-size: none;
--os-handle-perpendicular-size: 100%;
--os-handle-perpendicular-size-hover: 100%;
--os-handle-perpendicular-size-active: 100%;
--os-handle-interactive-area-offset: 0;
}
You can alter the properties either for both scrollbars at once or per scrollbar axis. In the example below I've chosen os-theme-custom
as the theme name:
.os-theme-custom {
--os-size: 10px;
}
.os-theme-custom.os-scrollbar-horizontal {
--os-size: 10px;
}
.os-theme-custom.os-scrollbar-vertical {
--os-size: 20px;
}
You can then use your theme by assigning it via the scrollbars.theme
option:
OverlayScrollbars(document.body, {
scrollbars: {
theme: 'os-theme-custom'
}
});
Since scrollbar styles are usually simple, this set of options should be enough to get your desired styling.
In case you need more freedom you can create your own styles by adding styling to the base class names described in the next section.
Scrollbars structure and CSS class names
The scrollbars HTML markup looks like:
<div class="os-scrollbar os-scrollbar-horizontal">
<div class="os-scrollbar-track">
<div class="os-scrollbar-handle">
</div>
</div>
</div>
<div class="os-scrollbar os-scrollbar-vertical">
<div class="os-scrollbar-track">
<div class="os-scrollbar-handle">
</div>
</div>
</div>
The class names are simplified, in a real application the .os-scrollbar
element can have additional class names which modify the appearance (mostly visibility and alignment).
Below is a list of the most important class names you will encounter:
CSS class name | description |
---|
.os-scrollbar | The root element of a scrollbar. |
.os-scrollbar-rtl | Indicates a RTL direction of the host element the scrollbar belongs to. |
.os-scrollbar-horizontal | The root element of a horizontal scrollbar. |
.os-scrollbar-vertical | The root element of a vertical scrollbar. |
.os-scrollbar-handle-interactive | Indicates that the handle inside the scrollbar is interactive (scrollbars.dragScroll is true ). |
.os-scrollbar-track-interactive | Indicates that the track inside the scrollbar is interactive (scrollbars.clickScroll is true ). |
.os-scrollbar-track | The track element. This is the track of the nested handle element. If scrollbars.clickScroll is true this is the element users can click to change the scroll offset. |
.os-scrollbar-handle | The handle element. If scrollbars.dragScroll is true this is the handle users can drag to change the scroll offset. |
If you create your own theme, please only use the classes listed above. All other classes are modifier classes used to change visibility, alignment and pointer-events of the scrollbars.
Gotchas
Its important that the chosen theme class name in your CSS file matches the assigned theme name in the options. If the CSS class name is .my-theme
the scrollbars.theme
has to be 'my-theme'
.
Please be aware of your stack. css-modules
for example will alter your class names to prevent naming collisions. Always double check if your CSS is really what you expect it to be.
Plugins
Everything thats considered not core functionality or old browser compatibility is exposed via a plugin. This is done because all unused plugins are treeshaken and thus won't end up in your final bundle. OverlayScrollbars comes with the following plugins:
Consuming Plugins
Plugins are consumed like:
import {
OverlayScrollbars,
ScrollbarsHidingPlugin,
SizeObserverPlugin,
ClickScrollPlugin
} from 'overlayscrollbars';
OverlayScrollbars.plugin(ScrollbarsHidingPlugin);
OverlayScrollbars.plugin([SizeObserverPlugin, ClickScrollPlugin]);
Plugins in depth
This is a in depth topic. Click here to read it.
Plugins are plain objects with a single field, the name of the field is the name of the plugin. This name is the plugins identifier and must be unique across all plugin. In case multiple plugins have the same name, the last added plugin overwrites previously added plugins.
Plugin Modules
A Plugin module is the constructor of a plugin modules instance. There are two kinds of plugin modules: static
and instance
. A single plugin must have one or more modules. Plugin modules can return an instance, but doesnt have to.
Static Plugin Module
The static
plugin module is invoked when the plugin is added with the OverlayScrollbars.plugin
function.
Example plugin with a static
module:
const staticPlugin = {
examplePlugin: {
static: (osStatic) => {
let count = 0;
const staticPluginModuleInstance = {
getCount: () => count,
increment: () => { count++ },
}
return staticPluginModuleInstance;
}
}
}
When the plugin is added with the OverlayScrollbars.plugin
function, the static module instance is returned:
const staticModuleInstance = OverlayScrollbars.plugin(staticPlugin);
staticModuleInstance.count;
staticModuleInstance.increment();
staticModuleInstance.count;
Instance Plugin Module
The instance
plugin module is invoked when a new OverlayScrollbars
instance is created but before the initialized
event is dispatched.
Example plugin with a instance
module:
const instancePlugin = {
examplePlugin: {
instance: (osInstance, event, osStatic) => {
let count = 0;
const instancePluginModuleInstance = {
getCount: () => count,
increment: () => { count++ },
}
event('initialized', () => {
console.log("instance initialized");
});
const removeScrollEvent = event('scroll', () => {
console.log("viewport scrolled");
removeScrollEvent();
});
return instancePluginModuleInstance;
}
}
}
When the plugin is added with the OverlayScrollbars.plugin
function all OverlayScrollbar instances will add the plugin automatically from that point on. Already created instances will not have the plugin. The instance modules instance is returned with the osInstance.plugin
function:
OverlayScrollbars.plugin(instancePlugin);
const osInstance = OverlayScrollbars(document.body, {});
const instancePluginInstance = osInstance.plugin(instancePlugin);
instancePluginInstance.count;
instancePluginInstance.increment();
instancePluginInstance.count;
TypeScript
export type Plugin<
Name extends string = string,
S extends PluginModuleInstance | void = PluginModuleInstance | void,
I extends PluginModuleInstance | void = PluginModuleInstance | void
> = {
[pluginName in Name]: PluginModule<S, I>;
};
export type StaticPlugin<
Name extends string = string,
T extends PluginModuleInstance = PluginModuleInstance
> = Plugin<Name, T, void>;
export type InstancePlugin<
Name extends string = string,
T extends PluginModuleInstance = PluginModuleInstance
> = Plugin<Name, void, T>;
export type InferStaticPluginModuleInstance<T extends StaticPlugin>;
export type InferInstancePluginModuleInstance<T extends InstancePlugin>;
FAQ
How do I get / set
the scroll position
of an element I applied OverlayScrollbars to?
If you applied OverlayScrollbars
to the body
element you can use window.scrollX
, window.scrollY
, window.scroll
, window.scrollTo
, window.scrollBy
or any other native api.
If the plugin was applied to any other element you have to get the viewport
element with the instance.elements()
function first. With this element you can use element.scrollTop
, element.scrollLeft
, element.scroll
, element.scrollTo
, element.scrollBy
or any other native api.
const { viewport } = osInstance.elements();
const { scrollLeft, scrollTop } = viewport;
viewport.scrollTo({ top: 0 });
Is it possible to limit / adjust the scrollbar handle length
?
You can adjust a scrollbars handle length by setting a min-width / min-height
and max-width / max-height
style:
.os-scrollbar-horizontal .os-scrollbar-handle {
min-width: 50px;
max-width: 200px;
}
.os-scrollbar-vertical .os-scrollbar-handle {
min-height: 40px;
max-height: 40px;
}
You can assign the same value to both properties to force the scrollbar to be always the same size.
Setting the width
and height
properties won't work since those are set by the plugin automatically.
Feature comparison to v1
- The
scroll
function is missing. Planned as a plugin
. (WIP) - Initialization to the
textarea
element isn't supported yet. Planned as a plugin
. (WIP)
|
Thanks to BrowserStack for sponsoring open source projects and letting me test OverlayScrollbars for free.
|
Future Plans
- Provide plugin based support for missing features. (treeshakeable)
- Frequent updates in terms of bug-fixes and enhancements. (always use latest browser features)
- Improve tests. (unit & browser tests)
License
MIT