vue-scrollbars
Version:
A light vue scrollbar comes from jquery.scrollbar.js
981 lines (884 loc) • 40.4 kB
JavaScript
import common from './util';
const BROWSER = {
body: document.querySelector('body'),
data: {
index: 0,
name: 'scrollbar',
},
firefox: /firefox/i.test(navigator.userAgent),
macosx: /mac/i.test(navigator.platform),
msedge: /edge\/\d+/i.test(navigator.userAgent),
msie: /(msie|trident)/i.test(navigator.userAgent),
mobile: /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent),
overlay: null, // check if browser overlays scrollbar which means the scrollbars totally floated
scroll: null, // restore browser scrollbar size info:scrollbar size
scrolls: new Set(), // restore mutiple instance of Scrollbar
webkit: /webkit/i.test(navigator.userAgent) && !/edge\/\d+/i.test(navigator.userAgent),
};
const DEFAULTS = {
autoScrollSize: true, // automatically calculate scrollsize
autoUpdate: true, // update scrollbar if content/container size changed
debug: false, // debug mode
disableBodyScroll: false, // disable body scroll if mouse over container
duration: 200, // scroll animate duration in ms
ignoreMobile: false, // ignore mobile devices
ignoreOverlay: false, // ignore browsers with overlay scrollbars (mobile, MacOS)
isRtl: false, // is RTL
scrollStep: 30, // scroll step for scrollbar arrows
showArrows: false, // add class to show arrows
stepScrolling: true, // when scrolling to scrollbar mousedown position
scrollx: null, // horizontal scroll element
scrolly: null, // vertical scroll element
onDestroy: null, // callback function on destroy,
onFallback: null, // callback function if scrollbar is not initialized
onInit: null, // callback function on first initialization
onScroll: null, // callback function on content scrolling
onUpdate: null, // callback function on init/resize (before scrollbar size calculation
};
const BAR_TYPES = {
advanced() {
// [ // For picture bar
// '<div class="scroll-element">',
// '<div class="scroll-element_corner"></div>',
// '<div class="scroll-arrow scroll-arrow_less"></div>',
// '<div class="scroll-arrow scroll-arrow_more"></div>',
// '<div class="scroll-element_outer">',
// '<div class="scroll-element_size"></div>', // required! used for scrollbar size calculation !
// '<div class="scroll-element_inner-wrapper">',
// '<div class="scroll-element_inner scroll-element_track">', // used for handling scrollbar click
// '<div class="scroll-element_inner-bottom"></div>',
// '</div>',
// '</div>',
// '<div class="scroll-bar">', // required
// '<div class="scroll-bar_body">',
// '<div class="scroll-bar_body-inner"></div>',
// '</div>',
// '<div class="scroll-bar_bottom"></div>',
// '<div class="scroll-bar_center"></div>',
// '</div>',
// '</div>',
// '</div>'
// ].join('');
let el = document.createElement('div');
util.addClass(el, 'scroll-element');
let el4corner = document.createElement('div');
util.addClass(el4corner, 'scroll-element_corner');
let el4arrowless = document.createElement('div');
util.addClass(el4arrowless, 'scroll-arrow scroll-element-arrow_less');
let el4arrowmore = document.createElement('div');
util.addClass(el4arrowmore, 'scroll-arrow scroll-element-arrow_more');
let el4outer = document.createElement('div');
util.addClass(el4outer, 'scroll-element_outer');
let el4size = document.createElement('div');
util.addClass(el4size, 'scroll-element_size');
let el4innerwrapper = document.createElement('div');
util.addClass(el4innerwrapper, 'scroll-element-inner_wrapper');
let el4inner = document.createElement('div');
util.addClass(el4inner, 'scroll-element-inner scroll-element_track');
let el4innerbottom = document.createElement('div');
util.addClass(el4innerbottom, 'scroll-element-inner_bottom');
let el4bar = document.createElement('div');
util.addClass(el4bar, 'scroll-element-bar');
let el4barbody = document.createElement('div');
util.addClass(el4barbody, 'scroll-element-bar_body');
let el4barbodyinner = document.createElement('div');
util.addClass(el4barbodyinner, 'scroll-element-bar_body-inner');
let el4bottom = document.createElement('div');
util.addClass(el4bottom, 'scroll-element-bottom');
let el4center = document.createElement('div');
util.addClass(el4center, 'scroll-element-center');
el4barbody.appendChild(el4barbodyinner);
el4bar.appendChild(el4barbody);
el4bar.appendChild(el4bottom);
el4bar.appendChild(el4center);
el4inner.appendChild(el4innerbottom);
el4innerwrapper.appendChild(el4inner);
el4outer.appendChild(el4size);
el4outer.appendChild(el4innerwrapper);
el4outer.appendChild(el4bar);
el.appendChild(el4corner);
el.appendChild(el4arrowless);
el.appendChild(el4arrowmore);
el.appendChild(el4outer);
return el;
},
simple() {
// [
// '<div class="scroll-element">',
// '<div class="scroll-element_outer">',
// '<div class="scroll-element_size"></div>', // required! used for scrollbar size calculation !
// '<div class="scroll-element_track"></div>', // used for handling scrollbar click
// '<div class="scroll-bar"></div>', // required
// '</div>',
// '</div>'
// ].join('')
let el = document.createElement('div');
util.addClass(el, 'scroll-element');
let el4outer = document.createElement('div');
util.addClass(el4outer, 'scroll-element_outer');
let el4size = document.createElement('div');
util.addClass(el4size, 'scroll-element_size');
let el4track = document.createElement('div');
util.addClass(el4track, 'scroll-element_track');
let el4bar = document.createElement('div');
util.addClass(el4bar, 'scroll-bar');
el.appendChild(el4outer);
el4outer.appendChild(el4size);
el4outer.appendChild(el4track);
el4outer.appendChild(el4bar);
return el;
},
};
let util = Object.assign(common, {
/* ADDITIONAL FUNCTIONS */
/**
* Get native browser scrollbar size (height/width)
*
* @param {Boolean} actual size or CSS size, default - CSS size
* @returns {Object} with height, width
*/
getBrowserScrollSize(actualSize) {
if (BROWSER.webkit && !actualSize) {
return {
height: 0,
width: 0,
};
}
if (!BROWSER.data.outer) {
let css = {
'border': 'none',
'box-sizing': 'content-box',
'height': '200px',
'margin': '0',
'padding': '0',
'width': '200px',
};
BROWSER.data.inner = document.createElement('div');
util.css(BROWSER.data.inner, css);
let outerCSS = Object.assign({
'left': '-1000px',
'overflow': 'scroll',
'position': 'absolute',
'top': '-1000px',
}, css);
BROWSER.data.outer = document.createElement('div');
util.css(BROWSER.data.outer, outerCSS);
BROWSER.data.outer.appendChild(BROWSER.data.inner);
BROWSER.body = BROWSER.body || document.querySelector('body');
BROWSER.body.appendChild(BROWSER.data.outer);
}
BROWSER.data.outer.scrollLeft = 1000;
BROWSER.data.outer.scrollTop = 1000;
let offsetInner = util.offset(BROWSER.data.inner);
let offsetOuter = util.offset(BROWSER.data.outer);
return {
height: Math.ceil((offsetOuter.top - offsetInner.top) || 0),
width: Math.ceil((offsetOuter.left - offsetInner.left) || 0),
};
},
/**
* Check if native browser scrollbars overlay content
*
* @returns {Boolean}
*/
isScrollOverlaysContent() {
var scrollSize = this.getBrowserScrollSize(true);
return !(scrollSize.height || scrollSize.width);
},
isVerticalScroll(event) {
var e = event;
if (e.axis && e.axis === e.HORIZONTAL_AXIS) {
return false;
}
if (e.wheelDeltaX) {
return false;
}
return true;
},
storeHandlers(inst, eventName, element, handler) {
inst._handlers.add({eventName, element, handler,});
},
removeHanlders(inst) {
inst._handlers.forEach((bean) => {
util.removeEvent(bean.element, bean.eventName, bean.handler);
});
},
storeScrollHandlers(inst, eventName, element, handler) {
inst._disabledScrollHandlers.add({eventName, element, handler,});
},
removeScrollHanlders(inst) {
inst._disabledScrollHandlers.forEach((bean) => {
util.removeEvent(bean.element, bean.eventName, bean.handler);
});
},
});
let debug = false;
/**
* Check if scroll content/container size is changed
*/
let updateScrollbars = (function () {
let timer = null, timerCounter = 0;
return function (force) {
BROWSER.scrolls.forEach((inst) => {
let options = inst.options;
let el = inst.el;
let wrapper = inst.wrapper;
let scrollx = inst.scrollx;
let scrolly = inst.scrolly;
if (force || (options.autoUpdate && wrapper &&
util.isVisible(wrapper) &&
(el.scrollWidth != scrollx.size ||
el.scrollHeight != scrolly.size ||
wrapper.clientWidth != scrollx.visible ||
wrapper.clientHeight != scrolly.visible
))) {
inst.update();
if (options.debug) {
window.console && console.log({
scrollHeight: el.scrollHeight + ':' + scrolly.size,
scrollWidth: el.scrollWidth + ':' + scrollx.size,
visibleHeight: wrapper.clientHeight + ':' + scrolly.visible,
visibleWidth: wrapper.clientWidth + ':' + scrollx.visible
}, true);
timerCounter++;
}
}
});
if (debug && timerCounter > 10) {
window.console && console.log('Scroll updates exceed 10');
updateScrollbars = function () {
};
} else {
clearTimeout(timer);
timer = setTimeout(updateScrollbars, 300);
}
};
})();
export default class Scrollbar {
constructor(el, wrapper) {
this.el = this.container = el;
this.ns = `.scrollbar_${BROWSER.data.index++}`;
this._originalWrapper = wrapper;
this.options = null;
this.scrollx = {}; // horizional bar
this.scrolly = {}; // vertical bar
this._handlers = new Set(); // restore event handlers which would be removed on destroy
this._disabledScrollHandlers = new Set();
el.__instance = this;
if (!BROWSER.scroll) {
BROWSER.overlay = util.isScrollOverlaysContent();
BROWSER.scroll = util.getBrowserScrollSize();
// monit scrollbar size changed
updateScrollbars();
// add global resize listener
let resize = util.throttle(function (e) {
let force = false;
if (BROWSER.scroll && (BROWSER.scroll.height || BROWSER.scroll.width)) {
let currentScroll = util.getBrowserScrollSize();
if (currentScroll.height != BROWSER.scroll.height || currentScroll.width != BROWSER.scroll.width) {
force = true;
BROWSER.scroll = currentScroll;
}
}
updateScrollbars(force);
});
util.addEvent(window, 'resize', resize);
util.storeHandlers(this, 'resize', window, resize);
}
BROWSER.scrolls.add(this);
}
init(options) {
let self = this;
let el = self.el;
let cw = self.containerWrapper || el;
let wrapper = self.wrapper;
let bar = {x: self.scrollx, y: self.scrolly,};
let initScroll = {
scrollLeft: self.el.scrollLeft,
scrollTop: self.el.scrollTop,
};
options = self.options = util.extend(true, {}, DEFAULTS, options);
// first init
// render dom structure
wrapper = self.wrapper = self._originalWrapper || document.createElement('div');
['scroll-wrapper', util.attr(el, 'class')].join(' ').split(/\s/).forEach((className) => {
return util.addClass(wrapper, className);
});
util.css(wrapper, {
position: util.css(el, 'position') === 'absolute' ? 'absolute' : 'relative',
});
!self._originalWrapper && util.insertBefore(wrapper, el).appendChild(el);
if (options.isRTL) {
util.addClass(wrapper, 'scroll--rtl');
}
// check if textarea
if (el.tagName.toLowerCase() === 'textarea') {
cw = self.containerWrapper = document.createElement('div');
util.insertBefore(cw, el).appendChild(el);
util.addClass(wrapper, 'scroll-textarea');
}
let css = {
height: 'auto',
'max-height': '',
'margin-bottom': `${BROWSER.scroll.height * -1}px`,
};
css[options.isRTL ? 'margin-left' : 'margin-right'] = `${BROWSER.scroll.width * -1}px`;
util.addClass(cw, 'scroll-content');
util.css(cw, css);
// scroll on el
function scroll4el(e) {
let scrollLeft = self.el.scrollLeft;
let scrollTop = self.el.scrollTop;
if (options.isRtl) {
// webkit 0:100
// ie/edge 100:0
// firefox -100:0
switch (true) {
case BROWSER.firefox:
scrollLeft = Math.abs(scrollLeft);
case BROWSER.msedge || BROWSER.msie:
scrollLeft = self.el.scrollWidth - self.el.clientWidth - scrollLeft;
break;
}
}
// onScroll handler
if (typeof options.onScroll === 'function') {
options.onScroll.call(self, {
maxScroll: bar.y.maxScrollOffset,
scroll: scrollTop,
size: bar.y.size,
visible: bar.y.visible,
}, {
maxScroll: bar.x.maxScrollOffset,
scroll: scrollLeft,
size: bar.x.size,
visible: bar.x.visible,
});
}
// TODO - It feels like zombie under IE, need smooth animation
bar.x.isVisible && (util.css(bar.x.scroll.bar, 'left', `${scrollLeft * bar.x.ratio}px`));
bar.y.isVisible && (util.css(bar.y.scroll.bar, 'top', `${scrollTop * bar.y.ratio}px`));
}
util.addEvent(el, 'scroll', scroll4el);
util.storeHandlers(self, 'scroll', el, scroll4el);
// prevent native scrollbars to be visible on #anchor click
function scroll4wrapper(e) {
wrapper.scrollTop = 0;
wrapper.scrollLeft = 0;
}
util.addEvent(wrapper, 'scroll', scroll4wrapper);
util.storeHandlers(self, 'scroll', wrapper, scroll4wrapper);
// if need disable body scroll
if (options.disableBodyScroll) {
var handleMouseScroll = function (event) {
util.isVerticalScroll(event) ?
bar.y.isVisible && bar.y.mousewheel(event) :
bar.x.isVisible && bar.x.mousewheel(event);
};
// util.addEvent(wrapper, 'MozMousePixelScroll', handleMouseScroll);
util.addEvent(wrapper, 'mousewheel', handleMouseScroll);
// util.storeHandlers(self, 'MozMousePixelScroll', wrapper, handleMouseScroll);
util.storeHandlers(self, 'mousewheel', wrapper, handleMouseScroll);
// util.storeScrollHandlers(self, 'MozMousePixelScroll', wrapper, handleMouseScroll);
util.storeScrollHandlers(self, 'mousewheel', wrapper, handleMouseScroll);
if (BROWSER.mobile) {
var touchstart4wrapper = function (e) {
let touch = e.originalEvent.touches && e.originalEvent.touches[0] || e;
let originalTouch = {
pageX: touch.pageX,
pageY: touch.pageY,
};
let originalScroll = {
left: touch.scrollLeft,
top: touch.scrollTop,
};
// touchmove
function touchmove4doc(e) {
let touch = e.originalEvent.touches && e.originalEvent.touches[0] || e;
el.scrollLeft = originalTouch.pageX + originalScroll.left - touch.pageX;
el.scrollTop = originalTouch.pageY + originalScroll.top - touch.pageY;
e.preventDefault();
}
util.addEvent(document, 'touchmove', touchmove4doc);
// touchend
function touchend4doc(e) {
util.removeEvent(wrapper, 'touchstart', touchstart4wrapper);
util.removeEvent(document, 'touchmove', touchmove4doc);
util.removeEvent(document, 'touchend', touchend4doc);
}
util.addEvent(document, 'touchend', touchend4doc);
};
util.addEvent(wrapper, 'touchstart', touchstart4wrapper);
util.storeHandlers(self, 'touchstart', wrapper, touchstart4wrapper);
util.storeScrollHandlers(self, 'touchstart', wrapper, touchstart4wrapper);
}
}
// callback for init
if (typeof options.onInit === 'function') {
options.onInit.call(self, el);
}
// render bars
self._renderBars(bar);
// reset styles
self._reset4BarCSS(bar);
// calc init sizes
self._calcInitSize4Bars(bar);
// update scrollbar visibility/dimensions
self._updateScroll('x', self.scrollx);
self._updateScroll('y', self.scrolly);
if (typeof options.onUpdate === 'function') {
options.onUpdate.apply(self, [self.el]);
}
// calc scrollbar size
self._calcSize4Bars(bar);
util.scrollLeft(self.el, initScroll.scrollLeft);
util.scrollTop(self.el, initScroll.scrollTop);
util.trigger(self.el, 'scroll');
}
update(options) {
let self = this;
let original = self.options;
options = self.options = util.extend(true, {}, original, options);
let bar = {x: self.scrollx, y: self.scrolly,};
let initScroll = {
scrollLeft: self.el.scrollLeft,
scrollTop: self.el.scrollTop,
};
let css = {
height: 'auto',
'max-height': '',
'margin-bottom': `${BROWSER.scroll.height * -1}px`,
};
css[options.isRTL ? 'margin-left' : 'margin-right'] = `${BROWSER.scroll.width * -1}px`;
util.css(self.containerWrapper || self.el, css);
// if need disable body scroll
if (options.disableBodyScroll && !original.disableBodyScroll) {
// add listener
let wrapper = self.wrapper;
var handleMouseScroll = function (event) {
util.isVerticalScroll(event) ?
bar.y.isVisible && bar.y.mousewheel(event) :
bar.x.isVisible && bar.x.mousewheel(event);
};
util.addEvent(wrapper, 'MozMousePixelScroll', handleMouseScroll);
util.addEvent(wrapper, 'mousewheel', handleMouseScroll);
util.storeHandlers(self, 'MozMousePixelScroll', wrapper, handleMouseScroll);
util.storeHandlers(self, 'mousewheel', wrapper, handleMouseScroll);
util.storeScrollHandlers(self, 'MozMousePixelScroll', wrapper, handleMouseScroll);
util.storeScrollHandlers(self, 'mousewheel', wrapper, handleMouseScroll);
if (BROWSER.mobile) {
let touchstart4wrapper = function (e) {
let touch = e.originalEvent.touches && e.originalEvent.touches[0] || e;
let originalTouch = {
pageX: touch.pageX,
pageY: touch.pageY,
};
let originalScroll = {
left: touch.scrollLeft,
top: touch.scrollTop,
};
// touchmove
function touchmove4doc(e) {
let touch = e.originalEvent.touches && e.originalEvent.touches[0] || e;
el.scrollLeft = originalTouch.pageX + originalScroll.left - touch.pageX;
el.scrollTop = originalTouch.pageY + originalScroll.top - touch.pageY;
e.preventDefault();
}
util.addEvent(document, 'touchmove', touchmove4doc);
// touchend
function touchend4doc(e) {
util.removeEvent(wrapper, 'touchstart', touchstart4wrapper);
util.removeEvent(document, 'touchmove', touchmove4doc);
util.removeEvent(document, 'touchend', touchend4doc);
}
util.addEvent(document, 'touchend', touchend4doc);
};
util.addEvent(wrapper, 'touchstart', touchstart4wrapper);
util.storeHandlers(self, 'touchstart', wrapper, touchstart4wrapper);
util.storeScrollHandlers(self, 'touchstart', wrapper, touchstart4wrapper);
}
} else if (!options.disableBodyScroll && original.disableBodyScroll) {
// remove listener
util.removeScrollHanlders(self);
}
// render bars
self._renderBars(bar);
// reset styles
self._reset4BarCSS(bar);
// calc init sizes
self._calcInitSize4Bars(bar);
// update scrollbar visibility/dimensions
self._updateScroll('x', self.scrollx);
self._updateScroll('y', self.scrolly);
if (typeof options.onUpdate === 'function') {
options.onUpdate.apply(self, [self.el]);
}
// calc scrollbar size
self._calcSize4Bars(bar);
// util.scrollLeft(self.el, initScroll.scrollLeft);
// util.scrollTop(self.el, initScroll.scrollTop);
self.el.scrollLeft = initScroll.scrollLeft;
self.el.scrollTop = initScroll.scrollTop;
util.trigger(self.el, 'scroll');
}
destroy() {
let self = this;
if (!self.wrapper) {
return;
}
delete self.el.__instance;
BROWSER.scrolls.delete(self);
// remove listeners
util.removeHanlders(self);
delete self._handlers;
// restore DOM structure
util.insertBefore(self.el, self.wrapper);
util.css(self.el, {
'height': '',
'margin': '',
'max-height': '',
});
'scroll-content scroll-scrollx_visible scroll-scrolly_visible'.split(/\s/).forEach((className) => {
util.removeClass(self.el, className);
});
util.removeClass(self.scrollx.scroll, 'scroll-scrollx_visible');
util.removeClass(self.scrolly.scroll, 'scroll-scrolly_visible');
if (!self._originalWrapper) {
self.wrapper.remove();
}
// onDestroy handler
if (typeof self.options.onDestroy === 'function') {
self.options.onDestroy.call(self, self.el);
}
}
_updateScroll(d, scrollx) {
var container = this.el,
containerWrapper = this.containerWrapper || container,
scrollClass = 'scroll-scroll' + d + '_visible',
scrolly = (d === 'x') ? this.scrolly : this.scrollx,
offset = parseInt(util.css(container, (d === 'x') ? 'left' : 'top'), 10) || 0,
wrapper = this.wrapper;
var AreaSize = scrollx.size;
var AreaVisible = scrollx.visible + offset;
scrollx.isVisible = (AreaSize - AreaVisible) > 1; // bug in IE9/11 with 1px diff
if (scrollx.isVisible) {
util.addClass(scrollx.scroll, scrollClass);
util.addClass(scrolly.scroll, scrollClass);
util.addClass(containerWrapper, scrollClass);
} else {
util.removeClass(scrollx.scroll, scrollClass);
util.removeClass(scrolly.scroll, scrollClass);
util.removeClass(containerWrapper, scrollClass);
}
if (d === 'y') {
if (container.tagName.toLowerCase() === 'textarea' || AreaSize < AreaVisible) {
util.css(containerWrapper, {
'height': (AreaVisible + BROWSER.scroll.height) + 'px',
'max-height': 'none',
});
} else {
util.css(containerWrapper, {
// "height": "auto", // do not reset height value: issue with height:100%!
'max-height': (AreaVisible + BROWSER.scroll.height) + 'px',
});
}
}
if (scrollx.size != container.scrollWidth ||
scrolly.size != container.scrollHeight ||
scrollx.visible != wrapper.clientWidth ||
scrolly.visible != wrapper.clientHeight ||
scrollx.offset != (parseInt(util.css(container, 'left'), 10) || 0) ||
scrolly.offset != (parseInt(util.css(container, 'top'), 10) || 0)
) {
Object.assign(this.scrollx, {
offset: parseInt(util.css(container, 'left'), 10) || 0,
size: container.scrollWidth,
visible: wrapper.clientWidth,
});
Object.assign(this.scrolly, {
offset: parseInt(util.css(container, 'top'), 10) || 0,
size: container.scrollHeight,
visible: wrapper.clientHeight,
});
this._updateScroll(d === 'x' ? 'y' : 'x', scrolly);
}
}
_reset4BarCSS(bar) {
let self = this;
// remove classes & reset applied styles
Object.keys(bar).sort().forEach(function (key) {
let item = bar[key];
var scrollClass = 'scroll-scroll' + key + '_visible';
var scrolly = (key == 'x') ? bar.y : bar.x;
util.removeClass(item.scroll, scrollClass);
util.removeClass(scrolly.scroll, scrollClass);
util.removeClass(self.containerWrapper || self.el, scrollClass);
});
}
_calcInitSize4Bars(bar) {
// remove classes & reset applied styles
let self = this;
Object.keys(bar).sort().forEach(function (key) {
let item = bar[key];
Object.assign(item, (key == 'x') ? {
offset: parseInt(util.css(self.el, 'left'), 10) || 0,
size: self.el.scrollWidth,
visible: self.wrapper.clientWidth,
} : {
offset: parseInt(util.css(self.el, 'top'), 10) || 0,
size: self.el.scrollHeight,
visible: self.wrapper.clientHeight,
});
});
}
_calcSize4Bars(bar) {
// calculate scroll size
let self = this;
Object.keys(bar).sort().forEach(function (key) {
let item = bar[key];
var cssOffset = (key === 'x') ? 'left' : 'top';
var cssFullSize = (key === 'x') ? 'width' : 'height';
var cssSize = (key === 'x') ? 'width' : 'height';
var offset = parseInt(util.css(self.el, cssOffset), 10) || 0;
var AreaSize = item.size;
var AreaVisible = item.visible + offset;
var scrollSize = parseInt(util.css(item.scroll.size, cssFullSize), 10) +
(parseInt(util.css(item.scroll.size, cssOffset), 10) || 0);
if (self.options.autoScrollSize) {
item.scrollbarSize = parseInt(scrollSize * AreaVisible / AreaSize, 10);
util.css(item.scroll.bar, cssSize, `${item.scrollbarSize}px`);
}
item.scrollbarSize = parseInt(util.css(item.scroll.bar, cssFullSize), 10);
item.ratio = ((scrollSize - item.scrollbarSize) / (AreaSize - AreaVisible)) || 1;
item.maxScrollOffset = AreaSize - AreaVisible;
});
}
/**
* 初始化滚动条
* @param bar
* @private
*/
_renderBars(bar) {
let self = this;
let options = self.options;
Object.keys(bar).sort().forEach((key) => {
let item = bar[key];
if (item.scroll) {
// rendered already
return;
}
// first init
let scrollCallback = null;
let scroll2Value = 0;
let scrollForward = 1;
let scrollStep = options.scrollStep;
let offsetPos = key === 'x' ? 'scrollLeft' : 'scrollTop';
let scrollTo = function () {
var currentOffset = self.el[offsetPos];
self.el[offsetPos] = (currentOffset + scrollStep);
if (scrollForward == 1 && (currentOffset + scrollStep) >= scroll2Value) {
currentOffset = self[offsetPos];
}
if (scrollForward == -1 && (currentOffset + scrollStep) <= scroll2Value) {
currentOffset = self[offsetPos];
}
if (self.el[offsetPos] == currentOffset && scrollCallback) {
scrollCallback();
}
};
let scroll = item.scroll = self._getBar(options[`scroll${key}`]);
util.addClass(scroll, `scroll-${key}`);
if (options.showArrows) {
util.addClass(scroll, 'scroll-element_arrows_visible');
}
item.mousewheel = function (e) {
if (!item.isVisible || (key === 'x' && util.isVerticalScroll(e))) {
return true;
}
if (key === 'y' && !util.isVerticalScroll(e)) {
bar.x.mousewheel(e);
return;
}
let delta = e.wheelDelta * -1 || e.detail;
let maxScrollValue = item.size - item.visible - item.offset;
// fix new mozilla
if (!delta) {
if (key === 'x' && !!e.deltaX) {
delta = e.deltaX * 40;
} else if (key === 'y' && !!e.deltaY) {
delta = e.deltaY * 40;
}
}
if ((delta > 0 && scroll2Value < maxScrollValue) ||
(delta < 0 && scroll2Value > 0)) {
scroll2Value = scroll2Value + delta;
if (scroll2Value < 0) {
scroll2Value = 0;
}
if (scroll2Value > maxScrollValue) {
scroll2Value = maxScrollValue;
}
self.scrollTo = self.scrollTo || {};
self.scrollTo[offsetPos] = scroll2Value;
if (self.scrollTo) {
// requestAnimationFrame(function () {
// self.el[offsetPos] = self.scrollTo[offsetPos];
// scroll2Value = self.el[offsetPos];
// self.scrollTo = null;
// })
// TODO - 当disableBodyScroll=true时,若用户快速滑动滚动条会出现延迟bug
util[offsetPos].call(util, self.el, self.scrollTo[offsetPos], function () {
scroll2Value = self.el[offsetPos];
self.scrollTo = null;
});
}
}
util.cancelBubble(e);
};
util.addEvent(item.scroll, 'wheel', item.mousewheel);
util.storeHandlers(self, 'wheel', item.scroll, item.mousewheel);
function mouseenter4scroll() {
scroll2Value = self.el[offsetPos];
}
util.addEvent(item.scroll, 'mouseenter', mouseenter4scroll);
util.storeHandlers(self, 'mouseenter', item.scroll, mouseenter4scroll);
// For .scroll-arrow and .scroll-element_track click
let mousedown = function (event) {
if (event.which != 1) {
return true;
}
scrollForward = 1;
var data = {
eventOffset: event[(key === 'x') ? 'pageX' : 'pageY'],
maxScrollValue: item.size - item.visible - item.offset,
scrollbarOffset: util.offset(item.scroll.bar)[(key === 'x') ? 'left' : 'top'],
scrollbarSize: item.scroll.bar[(key === 'x') ? 'offsetWidth' : 'offsetHeight'],
};
var timeout = 0, timer = 0;
if (util.hasClass(this, 'scroll-arrow')) {
scrollForward = util.hasClass(this, 'scroll-arrow_more') ? 1 : -1;
scrollStep = options.scrollStep * scrollForward;
scroll2Value = scrollForward > 0 ? data.maxScrollValue : 0;
if (options.isRtl) {
switch (true) {
case BROWSER.firefox:
scroll2Value = scrollForward > 0 ? 0 : data.maxScrollValue * -1;
break;
case BROWSER.msie || BROWSER.msedge:
break;
}
}
} else {
scrollForward = (data.eventOffset > (data.scrollbarOffset + data.scrollbarSize) ? 1
: (data.eventOffset < data.scrollbarOffset ? -1 : 0));
if (key === 'x' && options.isRtl && (BROWSER.msie || BROWSER.msedge)) {
scrollForward = scrollForward * -1;
}
scrollStep = Math.round(item.visible * 0.75) * scrollForward;
scroll2Value = (data.eventOffset - data.scrollbarOffset -
(options.stepScrolling ? (scrollForward == 1 ? data.scrollbarSize : 0)
: Math.round(data.scrollbarSize / 2)));
scroll2Value = self.el[offsetPos] + (scroll2Value / item.ratio);
}
self.scrollTo = self.scrollTo || {};
self.scrollTo[offsetPos] = options.stepScrolling ? self.el[offsetPos] + scrollStep : scroll2Value;
if (options.stepScrolling) {
scrollCallback = function () {
scroll2Value = self.el[offsetPos];
clearInterval(timer);
clearTimeout(timeout);
timeout = 0;
timer = 0;
};
timeout = setTimeout(function () {
timer = setInterval(scrollTo, 40);
}, options.duration + 100);
}
setTimeout(function () {
if (self.scrollTo) {
// c.animate(S.scrollTo, o.duration);
// TODO use css animation
// self.el[offsetPos]
util[offsetPos](self.scrollTo[offsetPos]);
self.scrollTo = null;
}
}, 1);
self._handleMouseDown(scrollCallback, event);
};
let el4scrollarrow = item.scroll.querySelector('.scroll-arrow');
el4scrollarrow && util.addEvent(el4scrollarrow, 'mousedown', mousedown);
el4scrollarrow && util.storeHandlers(self, 'mousedown', el4scrollarrow, mousedown);
util.addEvent(item.scroll.querySelector('.scroll-element_track'), 'mousedown', mousedown);
util.storeHandlers(self, 'mousedown', item.scroll.querySelector('.scroll-element_track'), mousedown);
// handle scrollbar drag'n'drop
function mousedown4bar(e) {
if (e.which != 1) {
return;
}
let pos = e[key === 'x' ? 'pageX' : 'pageY'];
let init4offset = self.el[offsetPos];
util.addClass(item.scroll, 'scroll-draggable');
function mousemove4doc(e) {
let diff = parseInt((e[key === 'x' ? 'pageX' : 'pageY'] - pos) / item.ratio, 10);
if (key === 'x' && options.isRtl && (BROWSER.msie || BROWSER.msedge)) {
diff = diff * -1;
}
self.el[offsetPos] = init4offset + diff;
}
util.addEvent(document, 'mousemove', mousemove4doc);
function mouseup4bar(e) {
util.removeEvent(document, 'mousemove', mousemove4doc);
util.removeEvent(document, 'mouseup', mouseup4bar);
util.cancelBubble(e);
}
util.addEvent(document, 'mouseup', mouseup4bar);
self._handleMouseDown(function () {
util.removeClass(item.scroll, 'scroll-draggable');
scroll2Value = self.el[offsetPos];
}, e);
}
util.addEvent(item.scroll.bar, 'mousedown', mousedown4bar);
util.storeHandlers(self, 'mousedown', item.scroll.bar, mousedown4bar);
});
}
_getBar(type) {
let tpl = null;
if (!type) {
tpl = BAR_TYPES['simple']();
}
if (typeof type === 'string') {
tpl = BAR_TYPES[typeof BAR_TYPES[type] === 'function' ? type : 'simple']();
} else if (type instanceof Element) {
tpl = type;
}
this.wrapper.appendChild(tpl);
tpl.bar = tpl.querySelector('.scroll-bar');
tpl.size = tpl.querySelector('.scroll-element_size');
tpl.track = tpl.querySelector('.scroll-element_track');
return tpl;
}
_handleMouseDown(callback, event, eventHandler) {
// let self = this;
function blur4doc(e) {
let body = document.querySelector('body');
util.removeEvent(document, 'dragstart', dragstart4doc);
util.removeEvent(body, 'selectstart', selectstart4doc);
util.removeEvent(document, 'blur', blur4doc);
callback && callback();
}
util.addEvent(document, 'blur', blur4doc);
// util.storeHandlers(self, 'blur', document, blur4doc);
function dragstart4doc(e) {
util.cancelBubble(e);
}
util.addEvent(document, 'dragstart', dragstart4doc);
// util.storeHandlers(self, 'dragstart', document, dragstart4doc);
util.addEvent(document, 'mouseup', blur4doc);
function selectstart4doc(e) {
util.cancelBubble(e);
}
util.addEvent(document.querySelector('body'), 'selectstart', selectstart4doc);
// util.storeHandlers(self, 'selectstart', document.querySelector('body'), selectstart4doc);
util.cancelBubble(event);
}
}