<div class="custom-scroller js-custom-scroller">
    <div class="custom-scroller__inner">
        <div class="custom-scroller__body">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fuga molestias, voluptas iste facere quos reprehenderit non unde voluptatibus quia sapiente ut, officia eius modi esse labore laboriosam magnam consectetur, necessitatibus rerum velit recusandae dolores autem alias aut! Pariatur eum accusantium optio illum deleniti consequuntur soluta, sequi expedita dolore ducimus earum quas nihil iure quam dolorem eveniet reiciendis praesentium. Aut ipsum ipsa voluptatibus tempora ea sunt qui, quae beatae sapiente nostrum. Sint voluptate temporibus hic labore sapiente iste porro iure nulla quia quaerat alias doloribus, dolores minima eveniet. Aliquid sunt illum architecto mollitia hic similique iusto quae, ratione quis soluta voluptate?</div>
    </div>
</div>
<div class="custom-scroller js-custom-scroller">
    <div class="custom-scroller__inner">
        {{#> @partial-block}}
            <div class="custom-scroller__body">{{ text }}</div>
        {{/@partial-block}}
    </div>
</div>
{
  "text": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fuga molestias, voluptas iste facere quos reprehenderit non unde voluptatibus quia sapiente ut, officia eius modi esse labore laboriosam magnam consectetur, necessitatibus rerum velit recusandae dolores autem alias aut! Pariatur eum accusantium optio illum deleniti consequuntur soluta, sequi expedita dolore ducimus earum quas nihil iure quam dolorem eveniet reiciendis praesentium. Aut ipsum ipsa voluptatibus tempora ea sunt qui, quae beatae sapiente nostrum. Sint voluptate temporibus hic labore sapiente iste porro iure nulla quia quaerat alias doloribus, dolores minima eveniet. Aliquid sunt illum architecto mollitia hic similique iusto quae, ratione quis soluta voluptate?"
}
  • Content:
    /*!
    * MiniBar 0.5.1
    * http://mobius.ovh/
    *
    * Released under the MIT license
    */
        const win = window,
            doc = document,
            body = doc.body,
    
            // Dimension terms
            trackPos = {
                x: 'left',
                y: 'top'
            },
            trackSize = {
                x: 'width',
                y: 'height'
            },
            scrollPos = {
                x: 'scrollLeft',
                y: 'scrollTop'
            },
            scrollSize = {
                x: 'scrollWidth',
                y: 'scrollHeight'
            },
            offsetSize = {
                x: 'offsetWidth',
                y: 'offsetHeight'
            },
            mAxis = {
                x: 'pageX',
                y: 'pageY'
            };
    
        /**
        * Object.assign polyfill
        * @param  {Object} target
        * @param  {Object} args
        * @return {Object}
        */
        const extend = function(r, t) {
            let e, n;
    
            for (e = Object(r), n = 1; n < arguments.length; n++) {
                const a = arguments[n];
                if (null != a)
                    for (const o in a) Object.prototype.hasOwnProperty.call(a, o) && (e[o] = a[o]);
            }
            return e;
        };
    
        const DOM = {
            /**
            * Mass assign style properties
            * @param  {Object} t
            * @param  {(String|Object)} e
            * @param  {String|Object}
            */
            css: function(t, e) {
                let i = t && t.style,
                    n = '[object Object]' === Object.prototype.toString.call(e);
                if (i) {
                    if (!e) return win.getComputedStyle(t);
                    n && each(e, function(t, e) {
                        t in i || (t = '-webkit-' + t);
                        i[t] = e + ('string' === typeof e ? '' : 'opacity' === t ? '' : 'px');
                    });
                }
            },
    
            /**
            * Get an element's DOMRect relative to the document instead of the viewport.
            * @param  {Object} t   HTMLElement
            * @param  {Boolean} e  Include margins
            * @return {Object}     Formatted DOMRect copy
            */
            rect: function(e) {
                const t = win,
                    o = e.getBoundingClientRect(),
                    b = doc.documentElement || body.parentNode || body,
                    d = (void 0 !== t.pageXOffset) ? t.pageXOffset : b.scrollLeft,
                    n = (void 0 !== t.pageYOffset) ? t.pageYOffset : b.scrollTop;
                return {
                    x: o.left + d,
                    y: o.top + n,
                    x2: o.left + o.width + d,
                    y2: o.top + o.height + n,
                    height: Math.round(o.height),
                    width: Math.round(o.width)
                };
            },
    
            /**
            * classList shim
            * @type {Object}
            */
            classList: {
                contains: function(s, a) {
                    if (s) return s.classList ? s.classList.contains(a) : !!s.className && !!s.className.match(new RegExp('(\\s|^)' + a + '(\\s|$)'));
                },
                add: function(s, a) {
                    DOM.classList.contains(s, a) || (s.classList ? s.classList.add(a) : s.className = s.className.trim() + ' ' + a);
                },
                remove: function(s, a) {
                    DOM.classList.contains(s, a) && (s.classList ? s.classList.remove(a) : s.className = s.className.replace(new RegExp('(^|\\s)' + a.split(' ').join('|') + '(\\s|$)', 'gi'), ' '));
                },
                toggle: function(s, a, c) {
                    const i = this.contains(s, a) ? !0 !== c && 'remove' : !1 !== c && 'add';
                    i && this[i](s, a);
                }
            },
    
            /**
            * Add event listener to target
            * @param  {Object} el
            * @param  {String} e
            * @param  {Function} fn
            */
            on: function(el, e, fn) {
                el.addEventListener(e, fn, false);
            },
    
            /**
            * Remove event listener from target
            * @param  {Object} el
            * @param  {String} e
            * @param  {Function} fn
            */
            off: function(el, e, fn) {
                el.removeEventListener(e, fn);
            },
    
            /**
            * Check is item array or array-like
            * @param  {Mixed} arr
            * @return {Boolean}
            */
            isCollection: function(arr) {
                return Array.isArray(arr) || arr instanceof HTMLCollection || arr instanceof NodeList;
            },
    
            /**
            * Get native scrollbar width
            * @return {Number} Scrollbar width
            */
            scrollWidth: function() {
                let t = 0,
                    e = doc.createElement('div');
                    e.style.cssText = 'width: 100; height: 100; overflow: scroll; position: absolute; top: -9999;';
                    doc.body.appendChild(e);
                    t = e.offsetWidth - e.clientWidth;
                    doc.body.removeChild(e);
                return t;
            }
        };
    
        /**
        * Iterator helper
        * @param  {(Array|Object)}   arr Any object, array or array-like collection.
        * @param  {Function} f   The callback function
        * @param  {Object}   s      Change the value of this
        * @return {Void}
        */
        const each = function(arr, fn, s) {
            if ('[object Object]' === Object.prototype.toString.call(arr)) {
                for (const d in arr) {
                    if (Object.prototype.hasOwnProperty.call(arr, d)) {
                        fn.call(s, d, arr[d]);
                    }
                }
            } else {
                for (let e = 0, f = arr.length; e < f; e++) {
                    fn.call(s, e, arr[e]);
                }
            }
        };
    
        /**
        * Returns a function, that, as long as it continues to be invoked, will not be triggered.
        * @param  {Function} fn
        * @param  {Number} wait
        * @param  {Boolean} now
        * @return {Function}
        */
        const debounce = function(n, t, u) {
            let e;
            return function() {
                let i = this,
                    o = arguments,
                    a = u && !e;
                clearTimeout(e);
                e = setTimeout(function() {
                    e = null;
                    u || n.apply(i, o);
                }, t);
                a && n.apply(i, o);
            };
        };
    
        const raf = win.requestAnimationFrame || (function() {
            let e = 0;
            return win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || function(n) {
                let t, i = (new Date()).getTime();
                t = Math.max(0, 16 - (i - e));
                e = i + t;
                return setTimeout(function() {
                    n(i + t);
                }, t);
            };
        }());
    
        const caf = win.cancelAnimationFrame || (function(id) {
            clearTimeout(id);
        }());
    
        /**
        * Main Library
        * @param {(String|Object)} content CSS3 selector string or node reference
        * @param {Object} options          User defined options
        */
        export default class MiniBar {
            constructor (container, options) {
            this.container = typeof container === 'string' ? doc.querySelector(container) : container;
    
            this.config = {
                barType: 'default',
                minBarSize: 10,
                alwaysShowBars: false,
                horizontalMouseScroll: false,
    
                scrollX: true,
                scrollY: true,
    
                navButtons: false,
                scrollAmount: 10,
    
                mutationObserver: {
                    attributes: false,
                    childList: true,
                    subtree: true
                },
    
                onInit: function() {},
                onUpdate: function() {},
                onStart: function() {},
                onScroll: function() {},
                onEnd: function() {},
    
                classes: {
                    container: 'mb-container',
                    content: 'mb-content',
                    track: 'mb-track',
                    bar: 'mb-bar',
                    visible: 'mb-visible',
                    progress: 'mb-progress',
                    hover: 'mb-hover',
                    scrolling: 'mb-scrolling',
                    textarea: 'mb-textarea',
                    wrapper: 'mb-wrapper',
                    nav: 'mb-nav',
                    btn: 'mb-button',
                    btns: 'mb-buttons',
                    increase: 'mb-increase',
                    decrease: 'mb-decrease',
                    item: 'mb-item',
                    itemVisible: 'mb-item-visible',
                    itemPartial: 'mb-item-partial',
                    itemHidden: 'mb-item-hidden'
                }
            };
    
            // User options
            if (options) {
                this.config = extend({}, this.config, options);
            } else if (win.MiniBarOptions) {
                this.config = extend({}, this.config, win.MiniBarOptions);
            }
    
            this.css = win.getComputedStyle(this.container);
    
            this.size = DOM.scrollWidth();
            this.textarea = this.container.nodeName.toLowerCase() === 'textarea';
    
            this.bars = {
                x: {},
                y: {}
            };
            this.tracks = {
                x: {},
                y: {}
            };
    
            this.lastX = 0;
            this.lastY = 0;
    
            this.scrollDirection = {
                x: 0,
                y: 0
            };
    
            // Events
            this.events = {};
    
            // Bind events
            const events = ['scroll', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'wheel'];
            for (let i = 0; i < events.length; i++) {
                this.events[events[i]] = this['_' + events[i]].bind(this);
            }
    
                    this.events.update = this.update.bind(this);
    
            // Debounce win resize
            this.events.debounce = debounce(this.events.update, 50);
    
            this.init();
            }
    
            /**
            * Init instance
            * @return {Void}
            */
            init() {
                const mb = this,
                    o = mb.config,
                    ev = mb.events;
    
                if (!mb.initialised) {
    
                    // We need a seperate wrapper for the textarea that we can pad
                    // otherwise the text will be up against the container edges
                    if (mb.textarea) {
                        mb.content = mb.container;
                        mb.container = doc.createElement('div');
                        DOM.classList.add(mb.container, o.classes.textarea);
    
                        mb.wrapper = doc.createElement('div');
                        DOM.classList.add(mb.wrapper, o.classes.wrapper);
                        mb.container.appendChild(mb.wrapper);
    
                        mb.content.parentNode.insertBefore(mb.container, mb.content);
    
                        // Update the bar on input
                        mb.content.addEventListener('input', function(e) {
                            mb.update();
                        });
    
                    } else {
                        mb.content = doc.createElement('div');
    
                        // Move all nodes to the the new content node
                        while (mb.container.firstChild) {
                            mb.content.appendChild(mb.container.firstChild);
                        }
                    }
    
                    DOM.classList.add(mb.container, o.classes.container);
                    DOM.classList.add(mb.content, o.classes.content);
    
                    if (o.alwaysShowBars) {
                        DOM.classList.add(mb.container, o.classes.visible);
                    }
    
                    // Set the tracks and bars and append them to the container
                    each(mb.tracks, function(axis, track) {
                        mb.bars[axis].node = doc.createElement('div');
                        track.node = doc.createElement('div');
    
                        DOM.classList.add(track.node, o.classes.track);
                        DOM.classList.add(track.node, o.classes.track + '-' + axis);
    
                        DOM.classList.add(mb.bars[axis].node, o.classes.bar);
                        track.node.appendChild(mb.bars[axis].node);
    
                        // Add nav buttons
                        if (o.navButtons) {
                            const dec = doc.createElement('button'),
                                inc = doc.createElement('button'),
                                wrap = doc.createElement('div'),
                                amount = o.scrollAmount;
    
                            dec.className = o.classes.btn + ' ' + o.classes.decrease;
                            inc.className = o.classes.btn + ' ' + o.classes.increase;
                            wrap.className = o.classes.btns + ' ' + o.classes.btns + '-' + axis;
    
                            wrap.appendChild(dec);
                            wrap.appendChild(track.node);
                            wrap.appendChild(inc);
    
                            mb.container.appendChild(wrap);
    
                            DOM.classList.add(mb.container, o.classes.nav);
    
                            // Mousedown on buttons
                            DOM.on(wrap, 'mousedown', function(e) {
                                const el = e.target;
    
                                caf(mb.frame);
    
                                if (el === inc || el === dec) {
    
                                    let scroll = mb.content[scrollPos[axis]];
    
                                    const move = function(c) {
                                        mb.content[scrollPos[axis]] = scroll;
                                        switch (el) {
                                            case dec:
                                                scroll -= amount;
                                                break;
                                            case inc:
                                                scroll += amount;
                                        }
                                        mb.frame = raf(move);
                                    };
    
                                    move();
                                }
    
                            });
    
                            // Mouseup on buttons
                            DOM.on(wrap, 'mouseup', function(e) {
                                const c = e.target,
                                    m = 5 * amount;
                                caf(mb.frame);
                                (c !== inc && c !== dec) || mb.scrollBy(c === dec ? -m : m, axis);
                            });
    
                        } else {
                            mb.container.appendChild(track.node);
                        }
    
                        if (o.barType === 'progress') {
                            DOM.classList.add(track.node, o.classes.progress);
    
                            DOM.on(track.node, 'mousedown', ev.mousedown);
                        } else {
                            DOM.on(track.node, 'mousedown', ev.mousedown);
                        }
    
                        DOM.on(track.node, 'mouseenter', function(e) {
                            DOM.classList.add(mb.container, o.classes.hover + '-' + axis);
                        });
    
                        DOM.on(track.node, 'mouseleave', function(e) {
                            if (!mb.down) {
                                DOM.classList.remove(mb.container, o.classes.hover + '-' + axis);
                            }
                        });
                    });
    
                    // Append the content
                    if (mb.textarea) {
                        mb.wrapper.appendChild(mb.content);
                    } else {
                        mb.container.appendChild(mb.content);
                    }
    
                    if (mb.css.position === 'static') {
                        mb.manualPosition = true;
                        mb.container.style.position = 'relative';
                    }
    
                    if (o.observableItems) {
                        const items = this.getItems();
    
                        if (items.length && 'IntersectionObserver' in window) {
                            mb.items = items;
    
                            const threshold = [];
    
                            // Increase / decrease to set granularity
                            const increment = 0.01;
    
                            // Don't want to have to type all of them...
                            for (let i = 0; i < 1; i += increment) {
                                threshold.push(i);
                            }
    
                            const callback = function(entries, observer) {
                                entries.forEach(entry => {
                                    const node = entry.target;
                                    const ratio = entry.intersectionRatio;
                                    const intersecting = entry.isIntersecting;
                                    const visible = intersecting && ratio >= 1;
                                    const hidden = !intersecting && ratio <= 0;
                                    const partial = intersecting && ratio > 0 && ratio < 1;
    
                                    DOM.classList.toggle(node, o.classes.itemVisible, visible);
                                    DOM.classList.toggle(node, o.classes.itemPartial, partial);
                                    DOM.classList.toggle(node, o.classes.itemHidden, hidden);
                                });
                            };
    
                            this.intersectionObserver = new IntersectionObserver(callback, {
                                root: null,
                                rootMargin: '0px',
                                threshold: threshold
                            });
    
                            each(items, function(i, item) {
                                mb.intersectionObserver.observe(item);
                            });
    
                        }
                    }
    
                    mb.update();
    
                    DOM.on(mb.content, 'scroll', ev.scroll);
                    DOM.on(mb.container, 'mouseenter', ev.mouseenter);
    
                    if (o.horizontalMouseScroll) {
                        DOM.on(mb.content, 'wheel', ev.wheel);
                    }
    
                    DOM.on(win, 'resize', ev.debounce);
    
                    DOM.on(doc, 'DOMContentLoaded', ev.update);
                    DOM.on(win, 'load', ev.update);
    
                    // check for MutationObserver support
                    if ('MutationObserver' in window) {
                        const callback = function(mutationsList, observer) {
                            if (mb.intersectionObserver) {
                                for (const mutation of mutationsList) {
                                    // update the instance if content changes
                                    if (mutation.type === 'childList') {
                                        //  observe / unobserve items
                                        for (const node of mutation.addedNodes) {
                                            mb.intersectionObserver.observe(node);
                                        }
    
                                        for (const node of mutation.removedNodes) {
                                            mb.intersectionObserver.unobserve(node);
                                        }
                                    }
                                }
                            }
    
                            if (mb.intersectionObserver) {
                                mb.items = mb.getItems();
                            }
    
                            // setTimeout(mb.update.bind(mb), 500);
                            mb.update();
                        };
    
                        this.mutationObserver = new MutationObserver(callback);
    
                        this.mutationObserver.observe(this.content, this.config.mutationObserver);
                    }
    
                    mb.initialised = true;
    
                    setTimeout(function() {
                        mb.config.onInit.call(mb, mb.getData());
                    }, 10);
                }
            };
    
            getItems() {
                const o = this.config;
                let items;
                if (typeof o.observableItems === 'string') {
                    items = this.content.querySelectorAll(o.observableItems);
                }
    
                if (o.observableItems instanceof HTMLCollection || o.observableItems instanceof NodeList) {
                    items = [].slice.call(o.observableItems);
                }
    
                return items;
            };
    
            /**
            * Get instance data
            * @return {Object}
            */
            getData(scrolling) {
                const c = this.content;
                const scrollTop = c.scrollTop;
                const scrollLeft = c.scrollLeft;
                const scrollHeight = c.scrollHeight;
                const scrollWidth = c.scrollWidth;
                const offsetWidth = c.offsetWidth;
                const offsetHeight = c.offsetHeight;
                const barSize = this.size;
                const containerRect = this.rect;
    
                return {
                    scrollTop,
                    scrollLeft,
                    scrollHeight,
                    scrollWidth,
                    offsetWidth,
                    offsetHeight,
                    containerRect,
                    barSize
                };
            };
    
            /**
            * Scroll content by amount
            * @param  {Number|String}     position   Position to scroll to
            * @param  {String}     axis     Scroll axis
            * @return {Void}
            */
            scrollTo(position, axis) {
    
                if (axis === undefined ) {
                            axis = 'y';
                        }
    
                let data = this.getData(),
                    amount;
    
                if (typeof position === 'string') {
                    if (position === 'start') {
                        amount = -data[scrollPos[axis]];
                    } else if (position === 'end') {
                        amount = data[scrollSize[axis]] - data[offsetSize[axis]] - data[scrollPos[axis]];
                    }
                } else {
                    amount = position - data[scrollPos[axis]];
                }
    
                this.scrollBy(amount, axis);
            };
    
            /**
            * Scroll content by amount
            * @param  {Number}     amount   Number of pixels to scroll
            * @param  {String}     axis     Scroll axis
            * @param  {Number}     duration Duration of scroll animation in ms
            * @param  {Function}   easing   Easing function
            * @return {Void}
            */
            scrollBy(amount, axis, duration, easing) {
    
                if (axis === undefined ) {
                            axis = 'y';
                        }
    
                // No animation
                if (duration === 0) {
                    this.content[scrollPos[axis]] += amount;
                    return;
                }
    
                // Duration of scroll
                if (duration === undefined) {
                    duration = 250;
                }
    
                // Easing function
                easing = easing || function(t, b, c, d) {
                    t /= d;
                    return -c * t * (t - 2) + b;
                };
    
                const mb = this,
                    st = Date.now(),
                    pos = mb.content[scrollPos[axis]];
    
                // Scroll function
                const scroll = function() {
                    const now = Date.now(),
                        ct = now - st;
    
                    // Cancel after allotted interval
                    if (ct > duration) {
                        caf(mb.frame);
                        mb.content[scrollPos[axis]] = Math.ceil(pos + amount);
                        return;
                    }
    
                    // Update scroll position
                    mb.content[scrollPos[axis]] = Math.ceil(easing(ct, pos, amount, duration));
    
                    // requestAnimationFrame
                    mb.frame = raf(scroll);
                };
    
                mb.frame = scroll();
            };
    
            /**
            * Scroll to top
            * @return {Void}
            */
            scrollToTop() {
                this.scrollTo(0);
            };
    
            /**
            * Scroll to bottom
            * @return {Void}
            */
            scrollToBottom() {
                const data = this.getData();
    
                this.scrollTo(data.scrollHeight - data.offsetHeight);
            };
    
            /**
            * Update cached values and recalculate sizes / positions
            * @param  {Object} e Event interface
            * @return {Void}
            */
            update() {
                const mb = this,
                    o = mb.config,
                    ct = mb.content,
                    s = mb.size;
    
                // Cache the dimensions
                mb.rect = DOM.rect(mb.container);
    
                mb.scrollTop = ct.scrollTop;
                mb.scrollLeft = ct.scrollLeft;
                mb.scrollHeight = ct.scrollHeight;
                mb.scrollWidth = ct.scrollWidth;
                mb.offsetWidth = ct.offsetWidth;
                mb.offsetHeight = ct.offsetHeight;
                mb.clientWidth = ct.clientWidth;
                mb.clientHeight = ct.clientHeight;
    
                // Do we need horizontal scrolling?
                const sx = mb.scrollWidth > mb.offsetWidth && !mb.textarea;
    
                // Do we need vertical scrolling?
                const sy = mb.scrollHeight > mb.offsetHeight;
    
                DOM.classList.toggle(mb.container, 'mb-scroll-x', sx && o.scrollX && !o.hideBars);
                DOM.classList.toggle(mb.container, 'mb-scroll-y', sy && o.scrollY && !o.hideBars);
    
                // Style the content
                DOM.css(ct, {
                    overflowX: sx ? 'auto' : '',
                    overflowY: sy ? 'auto' : '',
                    marginBottom: sx ? -s : '',
                    paddingBottom: sx ? s : '',
                    marginRight: sy ? -s : '',
                    paddingRight: sy && !o.hideBars ? s : ''
                });
    
                mb.scrollX = sx;
                mb.scrollY = sy;
    
                each(mb.tracks, function(i, track) {
                    extend(track, DOM.rect(track.node));
                    extend(mb.bars[i], DOM.rect(mb.bars[i].node));
                });
    
                // Update scrollbars
                mb.updateBars();
    
                mb.wrapperPadding = 0;
    
                if (mb.textarea) {
                    const css = DOM.css(mb.wrapper);
    
                    // Textarea wrapper has added padding
                    mb.wrapperPadding = parseInt(css.paddingTop, 10) + parseInt(css.paddingBottom, 10);
    
                    // Only scroll to bottom if the cursor is at the end of the content and we're not dragging
                    if (!mb.down && mb.content.selectionStart >= mb.content.value.length) {
                        mb.content.scrollTop = mb.scrollToBottom();
                    }
                }
    
                this.config.onUpdate.call(this, this.getData());
            };
    
            /**
            * Update a scrollbar's size and position
            * @param  {String} axis
            * @return {Void}
            */
            updateBar(axis) {
    
                const mb = this,
                    css = {},
                    ts = trackSize,
                    ss = scrollSize,
                    o = mb.config,
    
                    // Width or height of track
                    tsize = mb.tracks[axis][ts[axis]],
    
                    // Width or height of content
                    cs = mb.rect[ts[axis]] - mb.wrapperPadding,
    
                    // We need a live value, not cached
                    so = mb.content[scrollPos[axis]],
    
                    br = tsize / mb[ss[axis]],
                    sr = so / (mb[ss[axis]] - cs);
    
                if (o.barType === 'progress') {
                    // Only need to set the size of a progress bar
                    css[ts[axis]] = Math.floor(tsize * sr);
                } else {
                    // Set the scrollbar size
                    css[ts[axis]] = Math.max(Math.floor(br * cs), o.minBarSize);
    
                    // Set the scrollbar position
                    css[trackPos[axis]] = Math.floor((tsize - css[ts[axis]]) * sr);
                }
    
                raf(function() {
                    DOM.css(mb.bars[axis].node, css);
                });
            };
    
            /**
            * Update all scrollbars
            * @return {Void}
            */
            updateBars() {
                each(this.bars, function(i, v) {
                    this.updateBar(i);
                }, this);
            };
    
            /**
            * Destroy instance
            * @return {Void}
            */
            destroy() {
                const mb = this,
                    o = mb.config,
                    ct = mb.container;
    
                if (mb.initialised) {
    
                    // Remove the event listeners
                    DOM.off(ct, 'mouseenter', mb.events.mouseenter);
                    DOM.off(win, 'resize', mb.events.debounce);
    
                    // Remove the main classes from the container
                    DOM.classList.remove(ct, o.classes.visible);
                    DOM.classList.remove(ct, o.classes.container);
                    DOM.classList.remove(ct, o.classes.nav);
    
                    // Remove the tracks and / or buttons
                    each(mb.tracks, function(i, track) {
                        ct.removeChild(o.navButtons ? track.node.parentNode : track.node);
                        DOM.classList.remove(ct, 'mb-scroll-' + i);
                    });
    
                    // Move the nodes back to their original container
                    while (mb.content.firstChild) {
                        ct.appendChild(mb.content.firstChild);
                    }
    
                    // Remove the content node
                    ct.removeChild(mb.content);
    
                    // Remove manual positioning
                    if (mb.manualPosition) {
                        ct.style.position = '';
                    }
    
                    // Clear node references
                    mb.bars = {
                        x: {},
                        y: {}
                    };
                    mb.tracks = {
                        x: {},
                        y: {}
                    };
                    mb.content = null;
    
                    if (mb.mutationObserver) {
                        mb.mutationObserver.disconnect();
                        mb.mutationObserver = false;
                    }
    
                    if (o.observableItems) {
                        if (mb.intersectionObserver) {
                            mb.intersectionObserver.disconnect();
                            mb.intersectionObserver = false;
                        }
    
                        each(mb.items, function(i, item) {
                            const node = item.node || item;
                            DOM.classList.remove(node, o.classes.item);
                            DOM.classList.remove(node, o.classes.itemVisible);
                            DOM.classList.remove(node, o.classes.itemPartial);
                            DOM.classList.remove(node, o.classes.itemHidden);
                        });
                    }
    
                    mb.initialised = false;
                }
            };
    
            /* PRIVATE METHODS */
    
            /**
            * Scroll callback
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _scroll(e) {
                const data = this.getData(true);
    
                if (data.scrollLeft > this.lastX) {
                    this.scrollDirection.x = 1;
                } else if (data.scrollLeft < this.lastX) {
                    this.scrollDirection.x = -1;
                }
    
                if (data.scrollTop > this.lastY) {
                    this.scrollDirection.y = 1;
                } else if (data.scrollTop < this.lastY) {
                    this.scrollDirection.y = -1;
                }
    
                this.updateBars();
    
                this.config.onScroll.call(this, data);
    
                this.lastX = data.scrollLeft;
                this.lastY = data.scrollTop;
            };
    
            /**
            * Mousewheel callback
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _wheel(e) {
                e.preventDefault();
    
                this.scrollBy(e.deltaY * 100, 'x');
            };
    
            /**
            * Mouseenter callack
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _mouseenter(e) {
                this.updateBars();
            };
    
            /**
            * Mousedown callack
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _mousedown(e) {
                e.preventDefault();
    
                let mb = this,
                    o = mb.config,
                    type = o.barType === 'progress' ? 'tracks' : 'bars',
                    axis = e.target === mb[type].x.node ? 'x' : 'y';
    
                if (DOM.classList.contains(e.target, 'mb-track')) {
                    axis = e.target === mb.tracks.x.node ? 'x' : 'y';
                    const track = mb.tracks[axis];
                    const ts = track[trackSize[axis]];
                    const offset = e[mAxis[axis]] - track[axis];
                    const ratio = offset / ts;
                    const scroll = ratio * (mb.content[scrollSize[axis]] - mb.rect[trackSize[axis]]);
    
                    return this.scrollTo(scroll, axis);
                }
    
                mb.down = true;
    
                mb.currentAxis = axis;
    
                // Lets do all the nasty reflow-triggering stuff now
                // otherwise it'll be a shit-show during mousemove
                mb.update();
    
                // Keep the tracks visible during drag
                DOM.classList.add(mb.container, o.classes.visible);
                DOM.classList.add(mb.container, o.classes.scrolling + '-' + axis);
    
                // Save data for use during mousemove
                if (o.barType === 'progress') {
                    mb.origin = {
                        x: e.pageX - mb.tracks[axis].x,
                        y: e.pageY - mb.tracks[axis].y
                    };
                    mb._mousemove(e);
                } else {
                    mb.origin = {
                        x: e.pageX - mb.bars[axis].x,
                        y: e.pageY - mb.bars[axis].y
                    };
                };
    
                // Attach the mousemove and mouseup listeners now
                // instead of permanently having them on
                DOM.on(doc, 'mousemove', mb.events.mousemove);
                DOM.on(doc, 'mouseup', mb.events.mouseup);
            };
    
            /**
            * Mousemove callack
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _mousemove(e) {
                e.preventDefault();
    
                let mb = this,
                    o = this.origin,
                    axis = this.currentAxis,
                    track = mb.tracks[axis],
                    ts = track[trackSize[axis]],
                    offset, ratio, scroll,
                    progress = mb.config.barType === 'progress';
    
                offset = progress ? e[mAxis[axis]] - track[axis] : e[mAxis[axis]] - o[axis] - track[axis];
                ratio = offset / ts;
                scroll = progress ? ratio * (mb.content[scrollSize[axis]] - mb.rect[trackSize[axis]]) : ratio * mb[scrollSize[axis]];
    
                // Update scroll position
                raf(function() {
                    mb.content[scrollPos[axis]] = scroll;
                });
            };
    
            /**
            * Mouseup callack
            * @param  {Object} e Event interface
            * @return {Void}
            */
            _mouseup(e) {
                const mb = this,
                    o = mb.config,
                    ev = mb.events;
    
                DOM.classList.toggle(mb.container, o.classes.visible, o.alwaysShowBars);
                DOM.classList.remove(mb.container, o.classes.scrolling + '-' + mb.currentAxis);
    
                if (!DOM.classList.contains(e.target, o.classes.bar)) {
                    DOM.classList.remove(mb.container, o.classes.hover + '-x');
                    DOM.classList.remove(mb.container, o.classes.hover + '-y');
                }
    
                mb.currentAxis = null;
                mb.down = false;
    
                DOM.off(doc, 'mousemove', ev.mousemove);
                DOM.off(doc, 'mouseup', ev.mouseup);
            };
        };
    
  • URL: /components/raw/custom-scroller/CustomScroller.js
  • Filesystem Path: src/components/custom-scroller/CustomScroller.js
  • Size: 34.8 KB
  • Content:
    .custom-scroller {
      &__body {
        width: 2000px;
      }
    }
    
    .mb-container {
      overflow: hidden;
      width: 100%;
    }
    
    .mb-container:hover.mb-scroll-x>.mb-track-x,
    .mb-container:hover.mb-scroll-x>.mb-buttons-x,
    .mb-container:hover.mb-scroll-x>.mb-buttons-x>.mb-track-x,
    .mb-visible.mb-scroll-x>.mb-track-x,
    .mb-visible.mb-scroll-x>.mb-buttons-x,
    .mb-visible.mb-scroll-x>.mb-buttons-x>.mb-track-x {
      opacity: 1;
    }
    
    .mb-container:hover.mb-scroll-y>.mb-track-y,
    .mb-container:hover.mb-scroll-y>.mb-buttons-y,
    .mb-container:hover.mb-scroll-y>.mb-buttons-y>.mb-track-y,
    .mb-visible.mb-scroll-y>.mb-track-y,
    .mb-visible.mb-scroll-y>.mb-buttons-y,
    .mb-visible.mb-scroll-y>.mb-buttons-y>.mb-track-y {
      opacity: 1;
    }
    
    .mb-content {
      overflow: auto;
      height: 100%;
      width: auto;
      box-sizing: content-box;
      -ms-overflow-style: none;
      /* IE and Edge */
      scrollbar-width: none;
      /* Firefox */
    }
    
    .mb-content::-webkit-scrollbar,
    .mb-content::-webkit-scrollbar-track,
    .mb-content::-webkit-scrollbar-thumb {
      display: none;
    }
    
    .mb-track {
      position: absolute;
      -webkit-transition: opacity 250ms;
      transition: opacity 250ms;
    }
    
    .mb-bar {
      position: absolute;
      background: $color-green-dark;
      z-index: 10;
      -webkit-transform-style: preserve-3d;
      transform-style: preserve-3d;
    }
    
    .mb-track-x {
      left: 5px;
      bottom: 5px;
      width: calc(100% - 10px);
      height: 2px;
      opacity: 0;
      background: $color-gray-2;
    }
    
    .mb-track-x .mb-bar {
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      height: 4px;
      min-width: 10px;
      transition: .1s linear;
    }
    
    .mb-track-x .mb-bar:hover,
    .mb-track-x .mb-bar:focus-visible,
    .mb-track-x .mb-bar:active {
      height: 10px;
    }
    
    .mb-track-y {
      right: 5px;
      top: 5px;
      height: calc(100% - 10px);
      width: 2px;
      opacity: 0;
      background: $color-gray-2;
    }
    
    .mb-track-y .mb-bar {
      right: 50%;
      transform: translateX(50%);
      top: 0;
      width: 4px;
      min-height: 10px;
      transition: .1s linear;
    }
    
    .mb-track-y .mb-bar:hover,
    .mb-track-y .mb-bar:focus-visible,
    .mb-track-y .mb-bar:active {
      width: 10px;
    }
    
    .mb-progress {
      overflow: hidden;
    }
    
    .mb-progress .mb-bar {
      min-height: 0;
    }
    
    /* Buttons */
    .mb-buttons {
      position: absolute;
    }
    
    .mb-buttons-x {
      height: 16px;
      width: 100%;
      bottom: 0;
      left: 0;
      opacity: 0;
    }
    
    .mb-buttons-x .mb-decrease {
      left: 0;
    }
    
    .mb-buttons-x .mb-decrease::before {
      -webkit-transform: rotate(-135deg);
      transform: rotate(-135deg);
    }
    
    .mb-buttons-x .mb-increase {
      right: 0;
    }
    
    .mb-buttons-x .mb-increase::before {
      left: -1px;
      -webkit-transform: rotate(45deg);
      transform: rotate(45deg);
    }
    
    .mb-buttons-y {
      width: 16px;
      height: 100%;
      right: 0;
      top: 0;
      opacity: 0;
    }
    
    .mb-buttons-y .mb-decrease {
      top: 0;
    }
    
    .mb-buttons-y .mb-increase {
      bottom: 0;
    }
    
    .mb-buttons-y .mb-increase::before {
      top: 5px;
      -webkit-transform: rotate(135deg);
      transform: rotate(135deg);
    }
    
    .mb-nav .mb-bar {
      border-radius: 0;
      background-color: #969696;
    }
    
    .mb-nav .mb-bar:hover {
      background-color: #a3a3a3;
    }
    
    .mb-nav .mb-track {
      border-radius: 0;
      background-color: #d6d6d6;
    }
    
    .mb-nav .mb-track-x {
      left: 16px;
      height: 16px;
      width: calc(100% - 32px);
    }
    
    .mb-nav .mb-track-y {
      top: 16px;
      width: 16px;
      height: calc(100% - 32px);
    }
    
    .mb-button {
      border: none;
      width: 16px;
      height: 16px;
      position: absolute;
      padding: 0;
      background-color: #969696;
      cursor: pointer;
    }
    
    .mb-button:hover {
      background-color: #a3a3a3;
    }
    
    .mb-button::before {
      border-style: solid;
      border-width: 2px 2px 0 0;
      border-color: #fff;
      content: "";
      display: inline-block;
      height: 3px;
      width: 3px;
      left: 0px;
      top: 6px;
      position: relative;
      -webkit-transform: rotate(-45deg);
      transform: rotate(-45deg);
      vertical-align: top;
    }
    
    .mb-track-x .mb-decrease {
      left: 0;
    }
    
    .mb-track-x .mb-increase {
      right: 0;
    }
    
    .mb-track-y .mb-decrease {
      top: 0;
    }
    
    .mb-track-y .mb-increase {
      bottom: 0;
    }
    
    .mb-scroll-x .mb-buttons-y {
      height: calc(100% - 16px);
    }
    
    .mb-scroll-x .mb-buttons-y::before {
      position: absolute;
      left: 0;
      bottom: -16px;
      width: 100%;
      height: 100%;
      content: "";
      background-color: #fff;
      z-index: 0;
    }
    
    .mb-scroll-y .mb-buttons-x {
      width: calc(100% - 16px);
    }
    
    /* Textarea */
    .mb-wrapper {
      width: 100%;
      height: 100%;
      padding: 10px 0 10px 10px;
      box-sizing: border-box;
      background-color: #2E222D;
    }
    
    .mb-textarea .mb-content {
      border: none;
      width: 100%;
      height: 100%;
      margin: 0;
    }
    
    .mb-textarea .mb-track-x {
      display: none;
    }
    
    //Used in News List to get carousel on small screen
    .custom-scroller-list {
      margin: 24px 0;
      display: flex;
    
      @include breakpoint($s) {
        display: flex;
        flex-wrap: wrap;
        width: 100%;
      }
    }
  • URL: /components/raw/custom-scroller/custom-scroller.scss
  • Filesystem Path: src/components/custom-scroller/custom-scroller.scss
  • Size: 4.7 KB
  • Content:
    import CustomScroller from './CustomScroller';
    
    const els = document.querySelectorAll('.js-custom-scroller');
    
    for (let el of els) {
        new CustomScroller(el, {
            minBarSize: 50,
            barType: 'default', // or progress
            alwaysShowBars: true,
            horizontalMouseScroll: false,
            navButtons: false,
            scrollAmount: 10
        });
    }
    
  • URL: /components/raw/custom-scroller/index.js
  • Filesystem Path: src/components/custom-scroller/index.js
  • Size: 359 Bytes

No notes defined.