vuescroll
Version:
A powerful, customizable, multi-mode scrollbar plugin based on Vue.js
1,533 lines (1,301 loc) • 156 kB
JavaScript
/*
* Vuescroll v4.18.1
* (c) 2018-2023 Yi(Yves) Wang
* Released under the MIT License
* Github: https://github.com/YvesCoding/vuescroll
* Website: http://vuescrolljs.yvescoding.me/
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) :
typeof define === 'function' && define.amd ? define(['vue'], factory) :
(global = global || self, global.vuescroll = factory(global.Vue));
}(this, (function (Vue) { 'use strict';
Vue = Vue && Object.prototype.hasOwnProperty.call(Vue, 'default') ? Vue['default'] : Vue;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
function isIE() {
/* istanbul ignore if */
if (isServer()) return false;
var agent = navigator.userAgent.toLowerCase();
return agent.indexOf('msie') !== -1 || agent.indexOf('trident') !== -1 || agent.indexOf(' edge/') !== -1;
}
var isIos = function isIos() {
/* istanbul ignore if */
if (isServer()) return false;
var u = navigator.userAgent;
return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
};
/* istanbul ignore next */
var isServer = function isServer() {
return Vue.prototype.$isServer;
};
var touchManager = function () {
function touchManager() {
classCallCheck(this, touchManager);
}
createClass(touchManager, [{
key: 'getEventObject',
value: function getEventObject(originEvent) {
return this.touchObject ? this.isTouch ? originEvent.touches : [originEvent] : null;
}
}, {
key: 'getTouchObject',
value: function getTouchObject() /* istanbul ignore next */{
if (isServer()) return null;
this.isTouch = false;
var agent = navigator.userAgent,
platform = navigator.platform,
touchObject = {};
touchObject.touch = !!('ontouchstart' in window && !window.opera || 'msmaxtouchpoints' in window.navigator || 'maxtouchpoints' in window.navigator || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0);
touchObject.nonDeskTouch = touchObject.touch && !/win32/i.test(platform) || touchObject.touch && /win32/i.test(platform) && /mobile/i.test(agent);
touchObject.eventType = 'onmousedown' in window && !touchObject.nonDeskTouch ? 'mouse' : 'ontouchstart' in window ? 'touch' : 'msmaxtouchpoints' in window.navigator || navigator.msMaxTouchPoints > 0 ? 'mstouchpoints' : 'maxtouchpoints' in window.navigator || navigator.maxTouchPoints > 0 ? 'touchpoints' : 'mouse';
switch (touchObject.eventType) {
case 'mouse':
touchObject.touchstart = 'mousedown';
touchObject.touchend = 'mouseup';
touchObject.touchmove = 'mousemove';
touchObject.touchenter = 'mouseenter';
touchObject.touchmove = 'mousemove';
touchObject.touchleave = 'mouseleave';
break;
case 'touch':
touchObject.touchstart = 'touchstart';
touchObject.touchend = 'touchend';
touchObject.touchmove = 'touchmove';
touchObject.touchcancel = 'touchcancel';
touchObject.touchenter = 'touchstart';
touchObject.touchmove = 'touchmove';
touchObject.touchleave = 'touchend';
this.isTouch = true;
break;
case 'mstouchpoints':
touchObject.touchstart = 'MSPointerDown';
touchObject.touchend = 'MSPointerUp';
touchObject.touchmove = 'MSPointerMove';
touchObject.touchcancel = 'MSPointerCancel';
touchObject.touchenter = 'MSPointerDown';
touchObject.touchmove = 'MSPointerMove';
touchObject.touchleave = 'MSPointerUp';
break;
case 'touchpoints':
touchObject.touchstart = 'pointerdown';
touchObject.touchend = 'pointerup';
touchObject.touchmove = 'pointermove';
touchObject.touchcancel = 'pointercancel';
touchObject.touchenter = 'pointerdown';
touchObject.touchmove = 'pointermove';
touchObject.touchleave = 'pointerup';
break;
}
return this.touchObject = touchObject;
}
}]);
return touchManager;
}();
/**
* ZoomManager
* Get the browser zoom ratio
*/
var ZoomManager = function () {
function ZoomManager() {
var _this = this;
classCallCheck(this, ZoomManager);
this.originPixelRatio = this.getRatio();
this.lastPixelRatio = this.originPixelRatio;
window.addEventListener('resize', function () {
_this.lastPixelRatio = _this.getRatio();
});
}
createClass(ZoomManager, [{
key: 'getRatio',
value: function getRatio() {
var ratio = 0;
var screen = window.screen;
var ua = navigator.userAgent.toLowerCase();
if (window.devicePixelRatio !== undefined) {
ratio = window.devicePixelRatio;
} else if (~ua.indexOf('msie')) {
if (screen.deviceXDPI && screen.logicalXDPI) {
ratio = screen.deviceXDPI / screen.logicalXDPI;
}
} else if (window.outerWidth !== undefined && window.innerWidth !== undefined) {
ratio = window.outerWidth / window.innerWidth;
}
if (ratio) {
ratio = Math.round(ratio * 100);
}
return ratio;
}
}, {
key: 'getRatioBetweenPreAndCurrent',
value: function getRatioBetweenPreAndCurrent() {
return this.originPixelRatio / this.lastPixelRatio;
}
}]);
return ZoomManager;
}();
function deepCopy(from, to, shallow) {
if (shallow && isUndef(to)) {
return from;
}
if (isArray(from)) {
to = [];
from.forEach(function (item, index) {
to[index] = deepCopy(item, to[index]);
});
} else if (from) {
if (!isPlainObj(from)) {
return from;
}
to = {};
for (var key in from) {
to[key] = _typeof(from[key]) === 'object' ? deepCopy(from[key], to[key]) : from[key];
}
}
return to;
}
function mergeObject(from, to, force, shallow) {
if (shallow && isUndef(to)) {
return from;
}
to = to || {};
if (isArray(from)) {
if (!isArray(to) && force) {
to = [];
}
if (isArray(to)) {
from.forEach(function (item, index) {
to[index] = mergeObject(item, to[index], force, shallow);
});
}
} else if (from) {
if (!isPlainObj(from)) {
if (force) {
to = from;
}
} else {
for (var key in from) {
if (_typeof(from[key]) === 'object') {
if (isUndef(to[key])) {
to[key] = deepCopy(from[key], to[key], shallow);
} else {
mergeObject(from[key], to[key], force, shallow);
}
} else {
if (isUndef(to[key]) || force) to[key] = from[key];
}
}
}
}
return to;
}
function defineReactive(target, key, source, souceKey) {
/* istanbul ignore if */
if (!source[key] && typeof source !== 'function') {
return;
}
souceKey = souceKey || key;
Object.defineProperty(target, key, {
get: function get() {
return source[souceKey];
},
configurable: true
});
}
function eventCenter(dom, eventName, hander) {
var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var type = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'on';
type == 'on' ? dom.addEventListener(eventName, hander, capture) : dom.removeEventListener(eventName, hander, capture);
}
var error = function error(msg) {
console.error('[vuescroll] ' + msg);
};
var warn = function warn(msg) {
console.warn('[vuescroll] ' + msg);
};
function isChildInParent(child, parent) {
var flag = false;
if (!child || !parent) {
return flag;
}
while (child.parentNode !== parent && child.parentNode.nodeType !== 9 && !child.parentNode._isVuescroll) {
child = child.parentNode;
}
if (child.parentNode == parent) {
flag = true;
}
return flag;
}
function getPrefix(global) {
var docStyle = document.documentElement.style;
var engine;
/* istanbul ignore if */
if (global.opera && Object.prototype.toString.call(opera) === '[object Opera]') {
engine = 'presto';
} /* istanbul ignore next */else if ('MozAppearance' in docStyle) {
engine = 'gecko';
} else if ('WebkitAppearance' in docStyle) {
engine = 'webkit';
} /* istanbul ignore next */else if (typeof navigator.cpuClass === 'string') {
engine = 'trident';
}
var vendorPrefix = {
trident: 'ms',
gecko: 'moz',
webkit: 'webkit',
presto: 'O'
}[engine];
return vendorPrefix;
}
function getComplitableStyle(property, value) {
/* istanbul ignore if */
if (isServer()) return false;
var compatibleValue = '-' + getPrefix(window) + '-' + value;
var testElm = document.createElement('div');
testElm.style[property] = compatibleValue;
if (testElm.style[property] == compatibleValue) {
return compatibleValue;
}
/* istanbul ignore next */
return false;
}
/**
* Insert children into user-passed slot at vnode level
*/
function insertChildrenIntoSlot(h) {
var parentVnode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var childVNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var swapChildren = arguments[4];
/* istanbul ignore if */
if (parentVnode && parentVnode.length > 1) {
return swapChildren ? [].concat(toConsumableArray(childVNode), toConsumableArray(parentVnode)) : [].concat(toConsumableArray(parentVnode), toConsumableArray(childVNode));
}
parentVnode = parentVnode[0];
var _getVnodeInfo = getVnodeInfo(parentVnode),
ch = _getVnodeInfo.ch,
tag = _getVnodeInfo.tag,
isComponent = _getVnodeInfo.isComponent;
if (isComponent) {
parentVnode.data = mergeObject({ attrs: parentVnode.componentOptions.propsData }, parentVnode.data, false, // force: false
true // shallow: true
);
}
ch = swapChildren ? [].concat(toConsumableArray(childVNode), toConsumableArray(ch)) : [].concat(toConsumableArray(ch), toConsumableArray(childVNode));
delete parentVnode.data.slot;
return h(tag, mergeObject(data, parentVnode.data, false, true), ch);
}
/**
* Get the info of a vnode,
* vnode must be parentVnode
*/
function getVnodeInfo(vnode) {
if (!vnode || vnode.length > 1) return {};
vnode = vnode[0] ? vnode[0] : vnode;
var isComponent = !!vnode.componentOptions;
var ch = void 0;
var tag = void 0;
if (isComponent) {
ch = vnode.componentOptions.children || [];
tag = vnode.componentOptions.tag;
} else {
ch = vnode.children || [];
tag = vnode.tag;
}
return {
isComponent: isComponent,
ch: ch,
tag: tag
};
}
/**
* Get the vuescroll instance instead of
* user pass component like slot.
*/
function getRealParent(ctx) {
var parent = ctx.$parent;
if (!parent._isVuescrollRoot && parent) {
parent = parent.$parent;
}
return parent;
}
var isArray = function isArray(_) {
return Array.isArray(_);
};
var isPlainObj = function isPlainObj(_) {
return Object.prototype.toString.call(_) == '[object Object]';
};
var isUndef = function isUndef(_) {
return typeof _ === 'undefined';
};
function getNumericValue(distance, size) {
var number = void 0;
if (!(number = /(-?\d+(?:\.\d+?)?)%$/.exec(distance))) {
number = distance - 0;
} else {
number = number[1] - 0;
number = size * number / 100;
}
return number;
}
function createStyle(styleId, cssText) {
/* istanbul ignore if */
if (isServer() || document.getElementById(styleId)) {
return;
}
var head = document.head || doc.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.id = styleId;
style.type = 'text/css';
/* istanbul ignore if */
if (style.styleSheet) {
style.styleSheet.cssText = cssText;
} else {
style.appendChild(document.createTextNode(cssText));
}
head.appendChild(style);
}
// create slide mode style
function createSlideModeStyle() {
var cssText = '\n @-webkit-keyframes loading-rotate {\n to {\n -webkit-transform: rotate(1turn);\n transform: rotate(1turn);\n }\n }\n\n @keyframes loading-rotate {\n to {\n -webkit-transform: rotate(1turn);\n transform: rotate(1turn);\n }\n }\n\n @-webkit-keyframes loading-wipe {\n 0% {\n stroke-dasharray: 1, 200;\n stroke-dashoffset: 0;\n }\n 50% {\n stroke-dasharray: 90, 150;\n stroke-dashoffset: -40px;\n }\n to {\n stroke-dasharray: 90, 150;\n stroke-dashoffset: -120px;\n }\n }\n\n @keyframes loading-wipe {\n 0% {\n stroke-dasharray: 1, 200;\n stroke-dashoffset: 0;\n }\n 50% {\n stroke-dasharray: 90, 150;\n stroke-dashoffset: -40px;\n }\n to {\n stroke-dasharray: 90, 150;\n stroke-dashoffset: -120px;\n }\n }\n\n .__vuescroll .__refresh,\n .__vuescroll .__load {\n position: absolute;\n width: 100%;\n color: black;\n height: 50px;\n line-height: 50px;\n text-align: center;\n font-size: 16px;\n }\n .__vuescroll .__refresh svg,\n .__vuescroll .__load svg {\n margin-right: 10px;\n width: 25px;\n height: 25px;\n vertical-align: sub;\n }\n .__vuescroll .__refresh svg.active,\n .__vuescroll .__load svg.active {\n transition: all 0.2s;\n }\n .__vuescroll .__refresh svg.active.deactive,\n .__vuescroll .__load svg.active.deactive {\n transform: rotateZ(180deg);\n }\n .__vuescroll .__refresh svg path,\n .__vuescroll .__refresh svg rect,\n .__vuescroll .__load svg path,\n .__vuescroll .__load svg rect {\n fill: #20a0ff;\n }\n .__vuescroll .__refresh svg.start,\n .__vuescroll .__load svg.start {\n stroke: #343640;\n stroke-width: 4;\n stroke-linecap: round;\n -webkit-animation: loading-rotate 2s linear infinite;\n animation: loading-rotate 2s linear infinite;\n }\n .__vuescroll .__refresh svg.start .bg-path,\n .__vuescroll .__load svg.start .bg-path {\n stroke: #f2f2f2;\n fill: none;\n }\n .__vuescroll .__refresh svg.start .active-path,\n .__vuescroll .__load svg.start .active-path {\n stroke: #20a0ff;\n fill: none;\n stroke-dasharray: 90, 150;\n stroke-dashoffset: 0;\n -webkit-animation: loading-wipe 1.5s ease-in-out infinite;\n animation: loading-wipe 1.5s ease-in-out infinite;\n }\n ';
createStyle('vuescroll-silde-mode-style', cssText);
}
var api = {
mounted: function mounted() {
vsInstances[this._uid] = this;
},
beforeDestroy: function beforeDestroy() {
delete vsInstances[this._uid];
},
methods: {
// public api
scrollTo: function scrollTo(_ref, speed, easing) {
var x = _ref.x,
y = _ref.y;
// istanbul ignore if
if (speed === true || typeof speed == 'undefined') {
speed = this.mergedOptions.scrollPanel.speed;
}
this.internalScrollTo(x, y, speed, easing);
},
scrollBy: function scrollBy(_ref2, speed, easing) {
var _ref2$dx = _ref2.dx,
dx = _ref2$dx === undefined ? 0 : _ref2$dx,
_ref2$dy = _ref2.dy,
dy = _ref2$dy === undefined ? 0 : _ref2$dy;
var _getPosition = this.getPosition(),
_getPosition$scrollLe = _getPosition.scrollLeft,
scrollLeft = _getPosition$scrollLe === undefined ? 0 : _getPosition$scrollLe,
_getPosition$scrollTo = _getPosition.scrollTop,
scrollTop = _getPosition$scrollTo === undefined ? 0 : _getPosition$scrollTo;
if (dx) {
scrollLeft += getNumericValue(dx, this.scrollPanelElm.scrollWidth - this.$el.clientWidth);
}
if (dy) {
scrollTop += getNumericValue(dy, this.scrollPanelElm.scrollHeight - this.$el.clientHeight);
}
this.internalScrollTo(scrollLeft, scrollTop, speed, easing);
},
scrollIntoView: function scrollIntoView(elm) {
var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var parentElm = this.$el;
if (typeof elm === 'string') {
elm = parentElm.querySelector(elm);
}
if (!isChildInParent(elm, parentElm)) {
warn('The element or selector you passed is not the element of Vuescroll, please pass the element that is in Vuescroll to scrollIntoView API. ');
return;
}
// parent elm left, top
var _$el$getBoundingClien = this.$el.getBoundingClientRect(),
left = _$el$getBoundingClien.left,
top = _$el$getBoundingClien.top;
// child elm left, top
var _elm$getBoundingClien = elm.getBoundingClientRect(),
childLeft = _elm$getBoundingClien.left,
childTop = _elm$getBoundingClien.top;
var diffX = left - childLeft;
var diffY = top - childTop;
this.scrollBy({
dx: -diffX,
dy: -diffY
}, animate);
},
refresh: function refresh() {
this.refreshInternalStatus();
// refresh again to keep status is correct
this.$nextTick(this.refreshInternalStatus);
}
}
};
/** Public Api */
/**
* Refresh all
*/
var vsInstances = {};
function refreshAll() {
for (var vs in vsInstances) {
vsInstances[vs].refresh();
}
}
var baseConfig = {
// vuescroll
vuescroll: {
// vuescroll's size(height/width) should be a percent(100%)
// or be a number that is equal to its parentNode's width or
// height ?
sizeStrategy: 'percent',
/** Whether to detect dom resize or not */
detectResize: true,
/** Enable locking to the main axis if user moves only slightly on one of them at start */
locking: true
},
scrollPanel: {
// when component mounted.. it will automatically scrolls.
initialScrollY: false,
initialScrollX: false,
// feat: #11
scrollingX: true,
scrollingY: true,
speed: 300,
easing: undefined,
// Sometimes, the nativebar maybe on the left,
// See https://github.com/YvesCoding/vuescroll/issues/64
verticalNativeBarPos: 'right',
maxHeight: undefined,
maxWidth: undefined
},
//
rail: {
background: '#01a99a',
opacity: 0,
border: 'none',
/** Rail's size(Height/Width) , default -> 6px */
size: '6px',
/** Specify rail's border-radius, or the border-radius of rail and bar will be equal to the rail's size. default -> false **/
specifyBorderRadius: false,
/** Rail the distance from the two ends of the X axis and Y axis. **/
gutterOfEnds: null,
/** Rail the distance from the side of container. **/
gutterOfSide: '2px',
/** Whether to keep rail show or not, default -> false, event content height is not enough */
keepShow: false
},
bar: {
/** How long to hide bar after mouseleave, default -> 500 */
showDelay: 500,
/** Specify bar's border-radius, or the border-radius of rail and bar will be equal to the rail's size. default -> false **/
specifyBorderRadius: false,
/** Whether to show bar on scrolling, default -> true */
onlyShowBarOnScroll: true,
/** Whether to keep show or not, default -> false */
keepShow: false,
/** Bar's background , default -> #00a650 */
background: 'rgb(3, 185, 118)',
/** Bar's opacity, default -> 1 */
opacity: 1,
/** bar's size(Height/Width) , default -> 6px */
size: '6px',
minSize: 0,
disable: false
},
scrollButton: {
enable: false,
background: 'rgb(3, 185, 118)',
opacity: 1,
step: 180,
mousedownStep: 30
}
};
/**
* validate the options
* @export
* @param {any} ops
*/
function validateOps(ops) {
var renderError = false;
var scrollPanel = ops.scrollPanel;
var _ops$bar = ops.bar,
vBar = _ops$bar.vBar,
hBar = _ops$bar.hBar;
var _ops$rail = ops.rail,
vRail = _ops$rail.vRail,
hRail = _ops$rail.hRail;
// validate scrollPanel
var initialScrollY = scrollPanel['initialScrollY'];
var initialScrollX = scrollPanel['initialScrollX'];
if (initialScrollY && !String(initialScrollY).match(/^\d+(\.\d+)?(%)?$/)) {
warn('The prop `initialScrollY` or `initialScrollX` should be a percent number like `10%` or an exact number that greater than or equal to 0 like `100`.');
}
if (initialScrollX && !String(initialScrollX).match(/^\d+(\.\d+)?(%)?$/)) {
warn('The prop `initialScrollY` or `initialScrollX` should be a percent number like `10%` or an exact number that greater than or equal to 0 like `100`.');
}
// validate deprecated vBar/hBar vRail/hRail
if (vBar || hBar || vRail || hRail) {
warn('The options: vRail, hRail, vBar, hBar have been deprecated since v4.7.0,' + 'please use corresponing rail/bar instead!');
}
if (_extraValidate) {
_extraValidate = [].concat(_extraValidate);
_extraValidate.forEach(function (hasError) {
if (hasError(ops)) {
renderError = true;
}
});
}
return renderError;
}
var _extraValidate = null;
var extendOpts = function extendOpts(extraOpts, extraValidate) {
extraOpts = [].concat(extraOpts);
extraOpts.forEach(function (opts) {
mergeObject(opts, baseConfig);
});
_extraValidate = extraValidate;
};
// all modes
// do nothing
var NOOP$1 = function NOOP() {};
// some small changes.
var smallChangeArray = ['mergedOptions.vuescroll.pullRefresh.tips', 'mergedOptions.vuescroll.pushLoad.tips', 'mergedOptions.vuescroll.scroller.disable', 'mergedOptions.rail', 'mergedOptions.bar'];
// refresh/load dom ref/key...
var __REFRESH_DOM_NAME = 'refreshDom';
var __LOAD_DOM_NAME = 'loadDom';
var scrollMap = {
vertical: {
size: 'height',
opsSize: 'width',
posName: 'top',
opposName: 'bottom',
sidePosName: 'right',
page: 'pageY',
scroll: 'scrollTop',
scrollSize: 'scrollHeight',
offset: 'offsetHeight',
client: 'clientY',
axis: 'Y',
scrollButton: {
start: 'top',
end: 'bottom'
}
},
horizontal: {
size: 'width',
opsSize: 'height',
posName: 'left',
opposName: 'right',
sidePosName: 'bottom',
page: 'pageX',
scroll: 'scrollLeft',
scrollSize: 'scrollWidth',
offset: 'offsetWidth',
client: 'clientX',
axis: 'X',
scrollButton: {
start: 'left',
end: 'right'
}
}
};
function requestAnimationFrame(global) {
// Check for request animation Frame support
var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
var isNative = !!requestFrame;
if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
isNative = false;
}
if (isNative) {
return function (callback, root) {
requestFrame(callback, root);
};
}
var TARGET_FPS = 60;
var requests = {};
var rafHandle = 1;
var intervalHandle = null;
var lastActive = +new Date();
return function (callback) {
var callbackHandle = rafHandle++;
// Store callback
requests[callbackHandle] = callback;
// Create timeout at first request
if (intervalHandle === null) {
intervalHandle = setInterval(function () {
var time = +new Date();
var currentRequests = requests;
// Reset data structure before executing callbacks
requests = {};
for (var key in currentRequests) {
if (currentRequests.hasOwnProperty(key)) {
currentRequests[key](time);
lastActive = time;
}
}
// Disable the timeout when nothing happens for a certain
// period of time
if (time - lastActive > 2500) {
clearInterval(intervalHandle);
intervalHandle = null;
}
}, 1000 / TARGET_FPS);
}
return callbackHandle;
};
}
var colorCache = {};
var rgbReg = /rgb\(/;
var extractRgbColor = /rgb\((.*)\)/;
// Transform a common color int oa `rgbA` color
function getRgbAColor(color, opacity) {
var id = color + '&' + opacity;
if (colorCache[id]) {
return colorCache[id];
}
var div = document.createElement('div');
div.style.background = color;
document.body.appendChild(div);
var computedColor = window.getComputedStyle(div).backgroundColor;
document.body.removeChild(div);
/* istanbul ignore if */
if (!rgbReg.test(computedColor)) {
return color;
}
return colorCache[id] = 'rgba(' + extractRgbColor.exec(computedColor)[1] + ', ' + opacity + ')';
}
var bar = {
name: 'bar',
props: {
ops: Object,
state: Object,
hideBar: Boolean,
otherBarHide: Boolean,
type: String
},
computed: {
bar: function bar() {
return scrollMap[this.type];
},
barSize: function barSize() {
return Math.max(this.state.size, this.ops.bar.minSize);
},
barRatio: function barRatio() {
return (1 - this.barSize) / (1 - this.state.size);
}
},
render: function render(h) {
var _style, _style2, _barStyle;
var vm = this;
/** Get rgbA format background color */
var railBackgroundColor = getRgbAColor(vm.ops.rail.background, vm.ops.rail.opacity);
if (!this.touchManager) {
this.touchManager = new touchManager();
}
/** Rail Data */
var railSize = vm.ops.rail.size;
var endPos = vm.otherBarHide ? 0 : railSize;
var touchObj = vm.touchManager.getTouchObject();
var rail = {
class: '__rail-is-' + vm.type,
style: (_style = {
position: 'absolute',
'z-index': '1',
borderRadius: vm.ops.rail.specifyBorderRadius || railSize,
background: railBackgroundColor,
border: vm.ops.rail.border
}, defineProperty(_style, vm.bar.opsSize, railSize), defineProperty(_style, vm.bar.posName, vm.ops.rail['gutterOfEnds'] || 0), defineProperty(_style, vm.bar.opposName, vm.ops.rail['gutterOfEnds'] || endPos), defineProperty(_style, vm.bar.sidePosName, vm.ops.rail['gutterOfSide']), _style)
};
if (touchObj) {
var _rail$on;
rail.on = (_rail$on = {}, defineProperty(_rail$on, touchObj.touchenter, function () {
vm.setRailHover();
}), defineProperty(_rail$on, touchObj.touchleave, function () {
vm.setRailLeave();
}), _rail$on);
}
// left space for scroll button
var buttonSize = vm.ops.scrollButton.enable ? railSize : 0;
var barWrapper = {
class: '__bar-wrap-is-' + vm.type,
style: (_style2 = {
position: 'absolute',
borderRadius: vm.ops.rail.specifyBorderRadius || railSize
}, defineProperty(_style2, vm.bar.posName, buttonSize), defineProperty(_style2, vm.bar.opposName, buttonSize), _style2),
on: {}
};
var scrollDistance = vm.state.posValue * vm.state.size;
var pos = scrollDistance * vm.barRatio / vm.barSize;
var opacity = vm.state.opacity;
var parent = getRealParent(this);
// set class hook
parent.setClassHook(this.type == 'vertical' ? 'vBarVisible' : 'hBarVisible', !!opacity);
/** Scrollbar style */
var barStyle = (_barStyle = {
cursor: 'pointer',
position: 'absolute',
margin: 'auto',
transition: 'opacity 0.5s',
'user-select': 'none',
'border-radius': 'inherit'
}, defineProperty(_barStyle, vm.bar.size, vm.barSize * 100 + '%'), defineProperty(_barStyle, 'background', vm.ops.bar.background), defineProperty(_barStyle, vm.bar.opsSize, vm.ops.bar.size), defineProperty(_barStyle, 'opacity', opacity), defineProperty(_barStyle, 'transform', 'translate' + scrollMap[vm.type].axis + '(' + pos + '%)'), _barStyle);
var bar = {
style: barStyle,
class: '__bar-is-' + vm.type,
ref: 'thumb',
on: {}
};
if (vm.type == 'vertical') {
barWrapper.style.width = '100%';
// Let bar to be on the center.
bar.style.left = 0;
bar.style.right = 0;
} else {
barWrapper.style.height = '100%';
bar.style.top = 0;
bar.style.bottom = 0;
}
/* istanbul ignore next */
{
var _touchObj = this.touchManager.getTouchObject();
bar.on[_touchObj.touchstart] = this.createBarEvent();
barWrapper.on[_touchObj.touchstart] = this.createTrackEvent();
}
return h(
'div',
rail,
[this.createScrollbarButton(h, 'start'), this.hideBar ? null : h(
'div',
barWrapper,
[h('div', bar)]
), this.createScrollbarButton(h, 'end')]
);
},
data: function data() {
return {
isBarDragging: false
};
},
methods: {
setRailHover: function setRailHover() {
var parent = getRealParent(this);
var state = parent.vuescroll.state;
if (!state.isRailHover) {
state.isRailHover = true;
parent.showBar();
}
},
setRailLeave: function setRailLeave() {
var parent = getRealParent(this);
var state = parent.vuescroll.state;
state.isRailHover = false;
parent.hideBar();
},
setBarDrag: function setBarDrag(val) /* istanbul ignore next */{
this.$emit('setBarDrag', this.isBarDragging = val);
var parent = getRealParent(this);
// set class hook
parent.setClassHook(this.type == 'vertical' ? 'vBarDragging' : 'hBarDragging', !!val);
},
createBarEvent: function createBarEvent() {
var ctx = this;
var parent = getRealParent(ctx);
var touchObj = ctx.touchManager.getTouchObject();
function mousedown(e) /* istanbul ignore next */{
var event = ctx.touchManager.getEventObject(e);
if (!event) return;
e.stopImmediatePropagation();
e.preventDefault();
event = event[0];
document.onselectstart = function () {
return false;
};
ctx.axisStartPos = event[ctx.bar.client] - ctx.$refs['thumb'].getBoundingClientRect()[ctx.bar.posName];
// Tell parent that the mouse has been down.
ctx.setBarDrag(true);
eventCenter(document, touchObj.touchmove, mousemove);
eventCenter(document, touchObj.touchend, mouseup);
}
function mousemove(e) /* istanbul ignore next */{
if (!ctx.axisStartPos) {
return;
}
var event = ctx.touchManager.getEventObject(e);
if (!event) return;
event = event[0];
var thubmParent = ctx.$refs.thumb.parentNode;
var delta = event[ctx.bar.client] - thubmParent.getBoundingClientRect()[ctx.bar.posName];
delta = delta / ctx.barRatio;
var percent = (delta - ctx.axisStartPos) / thubmParent[ctx.bar.offset];
parent.scrollTo(defineProperty({}, ctx.bar.axis.toLowerCase(), parent.scrollPanelElm[ctx.bar.scrollSize] * percent), false);
}
function mouseup() /* istanbul ignore next */{
ctx.setBarDrag(false);
parent.hideBar();
document.onselectstart = null;
ctx.axisStartPos = 0;
eventCenter(document, touchObj.touchmove, mousemove, false, 'off');
eventCenter(document, touchObj.touchend, mouseup, false, 'off');
}
return mousedown;
},
createTrackEvent: function createTrackEvent() {
var ctx = this;
return function handleClickTrack(e) {
var parent = getRealParent(ctx);
var _ctx$bar = ctx.bar,
client = _ctx$bar.client,
offset = _ctx$bar.offset,
posName = _ctx$bar.posName,
axis = _ctx$bar.axis;
var thumb = ctx.$refs['thumb'];
e.preventDefault();
e.stopImmediatePropagation();
/* istanbul ignore if */
if (!thumb) return;
var barOffset = thumb[offset];
var event = ctx.touchManager.getEventObject(e)[0];
var percent = (event[client] - e.currentTarget.getBoundingClientRect()[posName] - barOffset / 2) / (e.currentTarget[offset] - barOffset);
parent.scrollTo(defineProperty({}, axis.toLowerCase(), percent * 100 + '%'));
};
},
// Scrollbuton relative things...
createScrollbarButton: function createScrollbarButton(h, type /* start or end */) {
var _style3;
var barContext = this;
if (!barContext.ops.scrollButton.enable) {
return null;
}
var size = barContext.ops.rail.size;
var _barContext$ops$scrol = barContext.ops.scrollButton,
opacity = _barContext$ops$scrol.opacity,
background = _barContext$ops$scrol.background;
var borderColor = getRgbAColor(background, opacity);
var wrapperProps = {
class: ['__bar-button', '__bar-button-is-' + barContext.type + '-' + type],
style: (_style3 = {}, defineProperty(_style3, barContext.bar.scrollButton[type], 0), defineProperty(_style3, 'width', size), defineProperty(_style3, 'height', size), defineProperty(_style3, 'position', 'absolute'), defineProperty(_style3, 'cursor', 'pointer'), defineProperty(_style3, 'display', 'table'), _style3),
ref: type
};
var innerProps = {
class: '__bar-button-inner',
style: {
border: 'calc(' + size + ' / 2.5) solid transparent',
width: '0',
height: '0',
margin: 'auto',
position: 'absolute',
top: '0',
bottom: '0',
right: '0',
left: '0'
},
on: {}
};
if (barContext.type == 'vertical') {
if (type == 'start') {
innerProps.style['border-bottom-color'] = borderColor;
innerProps.style['transform'] = 'translateY(-25%)';
} else {
innerProps.style['border-top-color'] = borderColor;
innerProps.style['transform'] = 'translateY(25%)';
}
} else {
if (type == 'start') {
innerProps.style['border-right-color'] = borderColor;
innerProps.style['transform'] = 'translateX(-25%)';
} else {
innerProps.style['border-left-color'] = borderColor;
innerProps.style['transform'] = 'translateX(25%)';
}
}
/* istanbul ignore next */
{
var touchObj = this.touchManager.getTouchObject();
innerProps.on[touchObj.touchstart] = this.createScrollButtonEvent(type, touchObj);
}
return h(
'div',
wrapperProps,
[h('div', innerProps)]
);
},
createScrollButtonEvent: function createScrollButtonEvent(type, touchObj) {
var ctx = this;
var parent = getRealParent(ctx);
var _ctx$ops$scrollButton = ctx.ops.scrollButton,
step = _ctx$ops$scrollButton.step,
mousedownStep = _ctx$ops$scrollButton.mousedownStep;
var stepWithDirection = type == 'start' ? -step : step;
var mousedownStepWithDirection = type == 'start' ? -mousedownStep : mousedownStep;
var ref = requestAnimationFrame(window);
// bar props: type
var barType = ctx.type;
var isMouseDown = false;
var isMouseout = true;
var timeoutId = void 0;
function start(e) {
/* istanbul ignore if */
if (3 == e.which) {
return;
}
// set class hook
parent.setClassHook('cliking' + barType + type + 'Button', true);
e.stopImmediatePropagation();
e.preventDefault();
isMouseout = false;
parent.scrollBy(defineProperty({}, 'd' + ctx.bar.axis.toLowerCase(), stepWithDirection));
eventCenter(document, touchObj.touchend, endPress, false);
if (touchObj.touchstart == 'mousedown') {
var elm = ctx.$refs[type];
eventCenter(elm, 'mouseenter', enter, false);
eventCenter(elm, 'mouseleave', leave, false);
}
clearTimeout(timeoutId);
timeoutId = setTimeout(function () /* istanbul ignore next */{
isMouseDown = true;
ref(pressing, window);
}, 500);
}
function pressing() /* istanbul ignore next */{
if (isMouseDown && !isMouseout) {
parent.scrollBy(defineProperty({}, 'd' + ctx.bar.axis.toLowerCase(), mousedownStepWithDirection), false);
ref(pressing, window);
}
}
function endPress() {
clearTimeout(timeoutId);
isMouseDown = false;
eventCenter(document, touchObj.touchend, endPress, false, 'off');
if (touchObj.touchstart == 'mousedown') {
var elm = ctx.$refs[type];
eventCenter(elm, 'mouseenter', enter, false, 'off');
eventCenter(elm, 'mouseleave', leave, false, 'off');
}
parent.setClassHook('cliking' + barType + type + 'Button', false);
}
function enter() /* istanbul ignore next */{
isMouseout = false;
pressing();
}
function leave() /* istanbul ignore next */{
isMouseout = true;
}
return start;
}
}
};
function getBarData(vm, type) {
var axis = scrollMap[type].axis;
/** type.charAt(0) = vBar/hBar */
var barType = type.charAt(0) + 'Bar';
var hideBar = !vm.bar[barType].state.size || !vm.mergedOptions.scrollPanel['scrolling' + axis] || vm.refreshLoad && type !== 'vertical' || vm.mergedOptions.bar.disable;
var keepShowRail = vm.mergedOptions.rail.keepShow;
if (hideBar && !keepShowRail) {
return null;
}
return {
hideBar: hideBar,
props: {
type: type,
ops: {
bar: vm.mergedOptions.bar,
rail: vm.mergedOptions.rail,
scrollButton: vm.mergedOptions.scrollButton
},
state: vm.bar[barType].state,
hideBar: hideBar
},
on: {
setBarDrag: vm.setBarDrag
},
ref: type + 'Bar',
key: type
};
}
/**
* create bars
*
* @param {any} size
* @param {any} type
*/
function createBar(h, vm) {
var verticalBarProps = getBarData(vm, 'vertical');
var horizontalBarProps = getBarData(vm, 'horizontal');
// set class hooks
vm.setClassHook('hasVBar', !!(verticalBarProps && !verticalBarProps.hideBar));
vm.setClassHook('hasHBar', !!(horizontalBarProps && !horizontalBarProps.hideBar));
return [verticalBarProps ? h('bar', _extends({}, verticalBarProps, {
props: _extends({ otherBarHide: !horizontalBarProps }, verticalBarProps.props)
})) : null, horizontalBarProps ? h('bar', _extends({}, horizontalBarProps, {
props: _extends({ otherBarHide: !verticalBarProps }, horizontalBarProps.props)
})) : null];
}
/**
* This is like a HOC, It extracts the common parts of the
* native-mode, slide-mode and mix-mode.
* Each mode must implement the following methods:
* 1. refreshInternalStatus : use to refresh the component
* 2. destroy : Destroy some registryed events before component destroy.
* 3. updateBarStateAndEmitEvent: use to update bar states and emit events.
*/
var createComponent = function createComponent(_ref) {
var _render = _ref.render,
components = _ref.components,
mixins = _ref.mixins;
return {
name: 'vueScroll',
props: {
ops: { type: Object }
},
components: components,
mixins: [api].concat(toConsumableArray([].concat(mixins))),
created: function created() {
var _this = this;
/**
* Begin to merge options
*/
var _gfc = mergeObject(this.$vuescrollConfig || {}, {});
var ops = mergeObject(baseConfig, _gfc);
this.$options.propsData.ops = this.$options.propsData.ops || {};
Object.keys(this.$options.propsData.ops).forEach(function (key) {
{
defineReactive(_this.mergedOptions, key, _this.$options.propsData.ops);
}
});
// from ops to mergedOptions
mergeObject(ops, this.mergedOptions);
this._isVuescrollRoot = true;
this.renderError = validateOps(this.mergedOptions);
},
render: function render(h) {
var vm = this;
if (vm.renderError) {
return h('div', [[vm.$slots['default']]]);
}
if (!vm.touchManager) vm.touchManager = new touchManager();
// vuescroll data
var data = {
style: {
height: vm.vuescroll.state.height,
width: vm.vuescroll.state.width,
padding: 0,
position: 'relative',
overflow: 'hidden'
},
class: _extends({ __vuescroll: true }, vm.classHooks)
};
var touchObj = vm.touchManager.getTouchObject();
if (touchObj) {
var _data$on;
data.on = (_data$on = {}, defineProperty(_data$on, touchObj.touchenter, function () {
vm.vuescroll.state.pointerLeave = false;
vm.updateBarStateAndEmitEvent();
vm.setClassHook('mouseEnter', true);
}), defineProperty(_data$on, touchObj.touchleave, function () {
vm.vuescroll.state.pointerLeave = true;
vm.hideBar();
vm.setClassHook('mouseEnter', false);
}), defineProperty(_data$on, touchObj.touchmove, function () /* istanbul ignore next */{
vm.vuescroll.state.pointerLeave = false;
vm.updateBarStateAndEmitEvent();
}), _data$on);
}
var ch = [_render(h, vm)].concat(toConsumableArray(createBar(h, vm)));
var _customContainer = this.$slots['scroll-container'];
if (_customContainer) {
return insertChildrenIntoSlot(h, _customContainer, ch, data);
}
return h(
'div',
data,
[ch]
);
},
mounted: function mounted() {
var _this2 = this;
if (!this.renderError) {
this.initVariables();
this.initWatchOpsChange();
// Call external merged Api
this.refreshInternalStatus();
this.updatedCbs.push(function () {
_this2.scrollToAnchor();
// need to reflow to deal with the
// latest thing.
_this2.updateBarStateAndEmitEvent();
});
}
},
updated: function updated() {
var _this3 = this;
this.updatedCbs.forEach(function (cb) {
cb.call(_this3);
});
// Clear
this.updatedCbs = [];
},
beforeDestroy: function beforeDestroy() {
if (this.destroy) {
this.destroy();
}
},
/** ------------------------------- Computed ----------------------------- */
computed: {
scrollPanelElm: function scrollPanelElm() {
return this.$refs['scrollPanel']._isVue ? this.$refs['scrollPanel'].$el : this.$refs['scrollPanel'];
}
},
data: function data() {
return {
vuescroll: {
state: {
isDragging: false,
pointerLeave: true,
isRailHover: false,
/** Default sizeStrategies */
height: '100%',
width: '100%',
// current size strategy
currentSizeStrategy: 'percent',
currentScrollState: null,
currentScrollInfo: null
}
},
bar: {
vBar: {
state: {
posValue: 0,
size: 0,
opacity: 0
}
},
hBar: {
state: {
posValue: 0,
size: 0,
opacity: 0
}
}
},
mergedOptions: {
vuescroll: {},
scrollPanel: {},
scrollContent: {},
rail: {},
bar: {}
},
updatedCbs: [],
renderError: false,
classHooks: {
hasVBar: false,
hasHBar: false,
vBarVisible: false,
hBarVisible: false,
vBarDragging: false,
hBarDragging: false,
clikingVerticalStartButton: false,
clikingVerticalEndButton: false,
clikingHorizontalStartButton: false,
clikingHorizontalEndButton: false,
mouseEnter: false
}
};
},
/** ------------------------------- Methods -------------------------------- */
methods: {
/** ------------------------ Handlers --------------------------- */
scrollingComplete: function scrollingComplete() {
this.updateBarStateAndEmitEvent('handle-scroll-complete');
},
setBarDrag: function setBarDrag(val) {
/* istanbul ignore next */
this.vuescroll.state.isDragging = val;
},
setClassHook: function setClassHook(name, value) {
this.classHooks[name] = value;
},
/** ------------------------ Some Helpers --------------------------- */
/*
* To have a good ux, instead of hiding bar immediately, we hide bar
* after some seconds by using this simple debounce-hidebar method.
*/
showAndDefferedHideBar: function showAndDefferedHideBar(forceHideBar) {
var _this4 = this;
this.showBar();
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = 0;
}
this.timeoutId = setTimeout(function () {
_this4.timeoutId = 0;
_this4.hideBar(forceHideBar);
}, this.mergedOptions.bar.showDelay);
},
showBar: function showBar() {
var opacity = this.mergedOptions.bar.opacity;
this.bar.vBar.state.opacity = opacity;
this.bar.hBar.state.opacity = opacity;
},
hideBar: function hideBar(forceHideBar) {
var _vuescroll$state = this.vuescroll.state,
isDragging = _vuescroll$state.isDragging,
isRailHover = _vuescroll$state.isRailHover;
/* istanbul ignore next */
if (isDragging || isRailHover) {
return;
}
if (forceHideBar && !this.mergedOptions.bar.keepShow) {
this