UNPKG

vuescroll

Version:

A powerful, customizable, multi-mode scrollbar plugin based on Vue.js

1,536 lines (1,306 loc) 171 kB
/* * 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 }); } var scrollBarWidth = void 0; var zoomManager = void 0; function getGutter() { /* istanbul ignore next */ if (isServer()) return 0; if (!zoomManager) { zoomManager = new ZoomManager(); } if (scrollBarWidth !== undefined) return scrollBarWidth * zoomManager.getRatioBetweenPreAndCurrent(); var outer = document.createElement('div'); outer.style.visibility = 'hidden'; outer.style.width = '100px'; outer.style.position = 'absolute'; outer.style.top = '-9999px'; document.body.appendChild(outer); var widthNoScroll = outer.offsetWidth; outer.style.overflow = 'scroll'; var inner = document.createElement('div'); inner.style.width = '100%'; outer.appendChild(inner); var widthWithScroll = inner.offsetWidth; outer.parentNode.removeChild(outer); scrollBarWidth = widthNoScroll - widthWithScroll; // multi the browser zoom if (!zoomManager) { zoomManager = new ZoomManager(); } return scrollBarWidth * zoomManager.getRatioBetweenPreAndCurrent(); } 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); } // Hide the ios native scrollbar. function createHideBarStyle() { /* istanbul ignore next */ { var cssText = '.__hidebar::-webkit-scrollbar {\n width: 0;\n height: 0;\n }'; createStyle('vuescroll-hide-ios-bar', cssText); } } // 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 var modes = ['slide', 'native']; // do nothing var NOOP = 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.c