<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?"
}
/*!
* 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);
};
};
.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%;
}
}
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
});
}
No notes defined.