jQuery floating-scroll Plugin Demo

/*!
 * jQuery floatingScroll Plugin v2.3.3
 * https://github.com/Amphiluke/floating-scroll
 * (c) 2017 Amphiluke
 */
(function (global, factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
        define(["jquery"], factory);
    } else if (typeof module === "object" && module.exports) {
        factory(require("jquery"));
    } else {
        factory(global.jQuery);
    }
}(this, function ($) {
    "use strict";

    function FScroll(cont) {
        var inst = this,
            scrollBody = cont.closest(".fl-scrolls-body");
        inst.cont = cont[0];
        if (scrollBody.length) {
            inst.scrollBody = scrollBody;
        }
        inst.sbar = inst.initScroll();
        inst.visible = true;
        inst.updateAPI(); // recalculate floating scrolls and hide those of them whose containers are out of sight
        inst.syncSbar(inst.cont);
        inst.addEventHandlers();
    }

    $.extend(FScroll.prototype, {
        initScroll: function () {
            var flscroll = $("
"); $("
").appendTo(flscroll).css({width: this.cont.scrollWidth + "px"}); return flscroll.appendTo(this.cont); }, addEventHandlers: function () { var inst = this, handlers, i, len; handlers = inst.eventHandlers = [ { $el: inst.scrollBody || $(window), handlers: { // Don't use `$.proxy()` since it makes impossible event unbinding individually per instance // (see the warning at http://api.jquery.com/unbind/) scroll: function () { inst.checkVisibility(); }, resize: function () { inst.updateAPI(); } } }, { $el: inst.sbar, handlers: { scroll: function () { inst.visible && inst.syncCont(this, true); } } }, { $el: $(inst.cont), handlers: { scroll: function () { inst.syncSbar(this, true); }, focusin: function () { setTimeout(function () { inst.syncSbar(inst.cont); }, 0); }, // The `adjustScroll` event type is kept for backward compatibility only. "update.fscroll adjustScroll": function (e) { // Check event namespace to ensure that this is not an extraneous event in a bubbling phase if (e.namespace === "fscroll" || e.type === "adjustScroll") { inst.updateAPI(); } }, "destroy.fscroll": function (e) { if (e.namespace === "fscroll") { inst.destroyAPI(); } } } } ]; for (i = 0, len = handlers.length; i < len; i++) { handlers[i].$el.bind(handlers[i].handlers); } }, checkVisibility: function () { var inst = this, mustHide = (inst.sbar[0].scrollWidth <= inst.sbar[0].offsetWidth), contRect, maxVisibleY; if (!mustHide) { contRect = inst.cont.getBoundingClientRect(); maxVisibleY = inst.scrollBody ? inst.scrollBody[0].getBoundingClientRect().bottom : window.innerHeight || document.documentElement.clientHeight; mustHide = ((contRect.bottom <= maxVisibleY) || (contRect.top > maxVisibleY)); } if (inst.visible === mustHide) { inst.visible = !mustHide; // we cannot simply hide a floating scroll bar since its scrollLeft property will not update in that case inst.sbar.toggleClass("fl-scrolls-hidden"); } }, syncCont: function (sender, preventSyncSbar) { // Prevents next syncSbar function from changing scroll position if (this.preventSyncCont === true) { this.preventSyncCont = false; return; } this.preventSyncSbar = !!preventSyncSbar; this.cont.scrollLeft = sender.scrollLeft; }, syncSbar: function (sender, preventSyncCont) { // Prevents next syncCont function from changing scroll position if (this.preventSyncSbar === true) { this.preventSyncSbar = false; return; } this.preventSyncCont = !!preventSyncCont; this.sbar[0].scrollLeft = sender.scrollLeft; }, // Recalculate scroll width and container boundaries updateAPI: function () { var inst = this, cont = inst.cont, pos = cont.getBoundingClientRect(); inst.sbar.width($(cont).outerWidth()); if (!inst.scrollBody) { inst.sbar.css("left", pos.left + "px"); } $("div", inst.sbar).width(cont.scrollWidth); inst.checkVisibility(); // fixes issue #2 }, // Remove a scrollbar and all related event handlers destroyAPI: function () { var handlers = this.eventHandlers, i, len; for (i = 0, len = handlers.length; i < len; i++) { handlers[i].$el.unbind(handlers[i].handlers); } this.sbar.remove(); } }); // `attachScroll` is the old alias used in v1.X. Temporally kept for backward compatibility. $.fn.attachScroll = $.fn.floatingScroll = function (method) { if (!arguments.length || method === "init") { this.each(function () { new FScroll($(this)); }); } else if (FScroll.prototype.hasOwnProperty(method + "API")) { this.trigger(method + ".fscroll"); } return this; }; }));