Handy dependency-free floating scrollbar widget

Introductory remarks

This web page contains a few examples of use of the handy-scroll widget. handy-scroll is a dependency-free module which can be used to solve the problem of scrolling some lengthy containers horizontally when those containers don’t fit into the viewport. The widget is just a scrollbar which is attached at the bottom of the container’s visible area. It doesn’t get out of sight when the page is scrolled, thereby making horizontal scrolling of the container much handier.

If you are looking for a guide on the module usage and API, please check out this README on GitHub.

#1: Handscrolls


Using of the handy-scroll widget can be appropriate for an online exhibiting of large handscrolls.

The demo below shows some use cases for the API methods update, destroy, and mounted. Click the “tabs” to switch over the paintings, and the widget parameters will update accordingly (note that handscrolls are viewed starting from the right end). Click “Disable/Enable” to destroy/remount the widget.

Live demo

Qingming Shanghe TuNine DragonsKangxi Emperor’s Southern Inspection Tour
“Along the River During the Qingming Festival” by Zhang Zeduan “Nine Dragons” by Chen Rong “Kangxi Emperor’s Southern Inspection Tour” by Wang Hui

Demo’s code

let handscrollsContainer = document.getElementById("handscrolls");
let handscrolls = handscrollsContainer.getElementsByTagName("img");
handscrollsContainer.scrollLeft = handscrollsContainer.scrollWidth;

// Widget initialization

document.getElementById("handscrolls-nav").addEventListener("click", ({target}) => {
    if (target.matches("span:not(.active)")) {
        Array.from(target.parentNode.children).forEach((span, index) => {
            let isActive = span === target;
            span.classList.toggle("active", isActive);
            handscrolls[index].classList.toggle("active", isActive);
        handscrollsContainer.scrollLeft = handscrollsContainer.scrollWidth;
        // Force widget update programmatically

document.getElementById("handscrolls-toggle").addEventListener("click", ({target}) => {
    // Checking whether the widget is mounted or not
    let mounted = handyScroll.mounted(handscrollsContainer);
    target.classList.toggle("disabled", mounted);
    // Unmount/remount the widget
    handyScroll[mounted ? "destroy" : "mount"](handscrollsContainer);

#2: handy-scroll widget in a popup


This example demonstrates a special use case — a widget inside a positioned popup. Please find out the details of this use case in the module’s docs.

Live demo

Click here to open a popup

“Along the River During the Qingming Festival”

“Along the River During the Qingming Festival” by Zhang Zeduan

“Along the River During the Qingming Festival” (fragment), painting by Zhang Zeduan (12th century)

Demo’s code (key parts)


<!-- Apply the "handy-scroll-viewport" class to the popup block -->
<div class="hs-popup hs-popup-hidden handy-scroll-viewport">
    <!-- Apply the "handy-scroll-body" class to the popup contents block -->
    <div class="handy-scroll-body">
        <!-- Horizontally scrollable container -->
        <div id="qingming-fest" class="hs-demo hs-qingming">
            <!-- Horizontally wide contents -->


.hs-popup {
    position:fixed; /* must be positioned (relative is the default) */
    /* other styles */
.hs-popup .handy-scroll-body {
    height:550px; /* same as for parent (the latter should not be scrollable) */
.hs-popup .handy-scroll:not(.handy-scroll-hidden) {
    bottom:10px; /* same value as the parent’s bottom padding */
    left:10px; /* same value as the parent’s left padding */


// Widget initialization
document.getElementById("hs-open-popup").addEventListener("click", e => {
    // Show the popup
    let popup = document.getElementById("hs-popup");
    // ...
    // You may need to force scrollbar update when the popup becomes visible

#3 “Unobtrusive” mode


This feature makes handy-scroll widgets appear only when the mouse pointer hovers over the scrollable container. Refer the module’s docs for more details.

Live demo

Check/uncheck this checkbox to toggle the “unobtrusive” mode for all widget instances on this page