UNPKG

vuescroll

Version:

A beautiful scrollbar based on Vue.js for PC and mobile.

1,545 lines (1,324 loc) 123 kB
/* * Vuescroll v4.6.9 * (c) 2018-2018 Yi(Yves) Wang * Released under the MIT License * Github Link: https://github.com/YvesCoding/vuescroll */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) : typeof define === 'function' && define.amd ? define(['vue'], factory) : (global.vuescroll = factory(global.Vue)); }(this, (function (Vue) { 'use strict'; Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue; 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 _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; }; function deepCopy(source, target) { target = (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target || {}; for (var key in source) { target[key] = _typeof(source[key]) === 'object' ? deepCopy(source[key], target[key] = {}) : source[key]; } return target; } function deepMerge(from, to) { to = to || {}; for (var key in from) { if (_typeof(from[key]) === 'object') { if (typeof to[key] === 'undefined') { to[key] = {}; deepCopy(from[key], to[key]); } else { deepMerge(from[key], to[key]); } } else { if (typeof to[key] === 'undefined') to[key] = from[key]; } } return to; } function defineReactive(target, key, source, souceKey) { var getter = null; /* istanbul ignore if */ if (!source[key] && typeof source !== 'function') { return; } souceKey = souceKey || key; if (typeof source === 'function') { getter = source; } Object.defineProperty(target, key, { get: getter || function () { return source[souceKey]; }, configurable: true }); } var scrollBarWidth = void 0; function getGutter() { /* istanbul ignore next */ if (Vue.prototype.$isServer) return 0; if (scrollBarWidth !== undefined) return scrollBarWidth; 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; return scrollBarWidth; } 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; } var pxValueReg = /(.*?)px/; function extractNumberFromPx(value) { var _return = pxValueReg.exec(value); return _return && _return[1]; } function isSupportTouch() { return 'ontouchstart' in window; } 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 isSupportGivenStyle(property, value) { 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; } function isIE() /* istanbul ignore next */{ var agent = navigator.userAgent.toLowerCase(); return agent.indexOf('msie') !== -1 || agent.indexOf('trident') !== -1 || agent.indexOf(' edge/') !== -1; } function insertChildrenIntoSlot(h, parentVnode, childVNode, data) { parentVnode = parentVnode[0] ? parentVnode[0] : parentVnode; var tag = parentVnode.componentOptions && parentVnode.componentOptions.tag || parentVnode.tag; // if (!Array.isArray(childVNode)) { // childVNode = [childVNode]; // } // // Remove null node // for (let index = 0; index < childVNode.length; index++) { // const element = childVNode[index]; // if (!element) { // childVNode.splice(index, 1); // index--; // } // } var _data = parentVnode.componentOptions || parentVnode.data || {}; // If component, use `nativeOn` intead. if (parentVnode.componentOptions) { data.nativeOn = data.on; _data.props = _data.propsData; delete data.on; delete data.propsData; } return h(tag, _extends({}, data, _data), childVNode); } function getRealParent(ctx) { var parent = ctx.$parent; if (!parent._isVuescrollRoot && parent) { parent = parent.$parent; } return parent; } // detect content size change function listenResize(element, callback) { return injectObject(element, callback); } function injectObject(element, callback) { if (element.hasResized) { return; } var OBJECT_STYLE = 'display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;'; // define a wrap due to ie's zIndex bug var objWrap = document.createElement('div'); objWrap.style.cssText = OBJECT_STYLE; var object = document.createElement('object'); object.style.cssText = OBJECT_STYLE; object.type = 'text/html'; object.tabIndex = -1; object.onload = function () { eventCenter(object.contentDocument.defaultView, 'resize', callback); }; // https://github.com/wnr/element-resize-detector/blob/aafe9f7ea11d1eebdab722c7c5b86634e734b9b8/src/detection-strategy/object.js#L159 if (!isIE()) { object.data = 'about:blank'; } objWrap.isResizeElm = true; objWrap.appendChild(object); element.appendChild(objWrap); if (isIE()) { object.data = 'about:blank'; } return function destroy() { if (object.contentDocument) { eventCenter(object.contentDocument.defaultView, 'resize', callback, 'off'); } element.removeChild(objWrap); element.hasResized = false; }; } // all modes var modes = ['slide', 'native', 'pure-native']; // do nothing var NOOP = function NOOP() {}; // some small changes. var smallChangeArray = ['mergedOptions.vuescroll.pullRefresh.tips', 'mergedOptions.vuescroll.pushLoad.tips', 'mergedOptions.rail', 'mergedOptions.bar']; var GCF = { // vuescroll vuescroll: { mode: 'native', // 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', // pullRefresh or pushLoad is only for the slide mode... pullRefresh: { enable: false, tips: { deactive: 'Pull to Refresh', active: 'Release to Refresh', start: 'Refreshing...', beforeDeactive: 'Refresh Successfully!' } }, pushLoad: { enable: false, tips: { deactive: 'Push to Load', active: 'Release to Load', start: 'Loading...', beforeDeactive: 'Load Successfully!' } }, paging: false, zooming: true, snapping: { enable: false, width: 100, height: 100 }, // some scroller options scroller: { /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ bouncing: true, /** Enable locking to the main axis if user moves only slightly on one of them at start */ locking: true, /** Minimum zoom level */ minZoom: 0.5, /** Maximum zoom level */ maxZoom: 3, /** Multiply or decrease scrolling speed **/ speedMultiplier: 1, /** This configures the amount of change applied to deceleration when reaching boundaries **/ penetrationDeceleration: 0.03, /** This configures the amount of change applied to acceleration when reaching boundaries **/ penetrationAcceleration: 0.08, /** Whether call e.preventDefault event when sliding the content or not */ preventDefault: true } }, scrollPanel: { // when component mounted.. it will automatically scrolls. initialScrollY: false, initialScrollX: false, // feat: #11 scrollingX: true, scrollingY: true, speed: 300, easing: undefined }, // scrollContent: { padding: false }, // rail: { vRail: { width: '6px', pos: 'right', background: '#01a99a', opacity: 0 }, // hRail: { height: '6px', pos: 'bottom', background: '#01a99a', opacity: 0 } }, bar: { showDelay: 500, vBar: { background: '#00a650', keepShow: false, opacity: 1, hover: false }, // hBar: { background: '#00a650', keepShow: false, opacity: 1, hover: false } } }; /** * validate the options * * @export * @param {any} ops */ function validateOptions(ops) { var shouldStopRender = false; var vuescroll = ops.vuescroll, scrollPanel = ops.scrollPanel; // validate vuescroll if (!~modes.indexOf(vuescroll.mode)) { error('The vuescroll\'s option "mode" should be one of the ' + modes); shouldStopRender = true; } if (vuescroll.paging == vuescroll.snapping.enable && vuescroll.paging && (vuescroll.pullRefresh || vuescroll.pushLoad)) { error('paging, snapping, (pullRefresh with pushLoad) can only one of them to be true.'); } // validate scrollPanel var initialScrollY = scrollPanel['initialScrollY']; var initialScrollX = scrollPanel['initialScrollX']; if (initialScrollY && !String(initialScrollY).match(/^\d+(\.\d+)?(%)?$/)) { error('The prop `initialScrollY` 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+)?(%)?$/)) { error('The prop `initialScrollX` should be a percent number like 10% or an exact number that greater than or equal to 0 like 100.'); } return shouldStopRender; } /** * hack the lifeCycle * * to merge the global data into user-define data */ function hackPropsData() { var vm = this; if (vm.$options.name === 'vueScroll') { var _gfc = deepMerge(vm.$vuescrollConfig, {}); var ops = deepMerge(GCF, _gfc); vm.$options.propsData.ops = vm.$options.propsData.ops || {}; Object.keys(vm.$options.propsData.ops).forEach(function (key) { { defineReactive(vm.mergedOptions, key, vm.$options.propsData.ops); } }); // from ops to mergedOptions deepMerge(ops, vm.mergedOptions); var prefix = 'padding-'; defineReactive(vm.mergedOptions.scrollContent, 'paddPos', function () { return prefix + vm.mergedOptions.rail.vRail.pos; }); defineReactive(vm.mergedOptions.scrollContent, 'paddValue', function () { return vm.mergedOptions.rail.vRail.width; }); } } var hackLifecycle = { data: function data() { return { shouldStopRender: false, mergedOptions: { vuescroll: {}, scrollPanel: {}, scrollContent: {}, rail: {}, bar: {} } }; }, created: function created() { hackPropsData.call(this); this._isVuescrollRoot = true; this.renderError = validateOptions(this.mergedOptions); } }; /** * Compatible to scroller's animation function */ function createEasingFunction(easing, easingPattern) { return function (time) { return easingPattern(easing, time); }; } /** * Calculate the easing pattern * @link https://github.com/cferdinandi/smooth-scroll/blob/master/src/js/smooth-scroll.js * modified by wangyi7099 * @param {String} type Easing pattern * @param {Number} time Time animation should take to complete * @returns {Number} */ function easingPattern(easing, time) { var pattern = null; /* istanbul ignore next */ { // Default Easing Patterns if (easing === 'easeInQuad') pattern = time * time; // accelerating from zero velocity if (easing === 'easeOutQuad') pattern = time * (2 - time); // decelerating to zero velocity if (easing === 'easeInOutQuad') pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration if (easing === 'easeInCubic') pattern = time * time * time; // accelerating from zero velocity if (easing === 'easeOutCubic') pattern = --time * time * time + 1; // decelerating to zero velocity if (easing === 'easeInOutCubic') pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration if (easing === 'easeInQuart') pattern = time * time * time * time; // accelerating from zero velocity if (easing === 'easeOutQuart') pattern = 1 - --time * time * time * time; // decelerating to zero velocity if (easing === 'easeInOutQuart') pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * --time * time * time * time; // acceleration until halfway, then deceleration if (easing === 'easeInQuint') pattern = time * time * time * time * time; // accelerating from zero velocity if (easing === 'easeOutQuint') pattern = 1 + --time * time * time * time * time; // decelerating to zero velocity if (easing === 'easeInOutQuint') pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * --time * time * time * time * time; // acceleration until halfway, then deceleration } return pattern || time; // no easing, no acceleration } 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; requestCount++; // 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 = {}; requestCount = 0; 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; }; } /* * Scroller * http://github.com/zynga/scroller * * Copyright 2011, Zynga Inc. * Licensed under the MIT License. * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt * * Based on the work of: Unify Project (unify-project.org) * http://unify-project.org * Copyright 2011, Deutsche Telekom AG * License: MIT + Apache (V2) */ /** * Generic animation class with support for dropped frames both optional easing and duration. * * Optional duration is useful when the lifetime is defined by another condition than time * e.g. speed of an animating object, etc. * * Dropped frame logic allows to keep using the same updater logic independent from the actual * rendering. This eases a lot of cases where it might be pretty complex to break down a state * based on the pure time difference. */ var time = Date.now || function () { return +new Date(); }; var desiredFrames = 60; var millisecondsPerSecond = 1000; var running = {}; var counter = 1; var core = { effect: {} }; var global = null; if (typeof window !== 'undefined') { global = window; } else { global = {}; } core.effect.Animate = { /** * A requestAnimationFrame wrapper / polyfill. * * @param callback {Function} The callback to be invoked before the next repaint. * @param root {HTMLElement} The root element for the repaint */ requestAnimationFrame: requestAnimationFrame(global), /** * Stops the given animation. * * @param id {Integer} Unique animation ID * @return {Boolean} Whether the animation was stopped (aka, was running before) */ stop: function stop(id) { var cleared = running[id] != null; if (cleared) { running[id] = null; } return cleared; }, /** * Whether the given animation is still running. * * @param id {Integer} Unique animation ID * @return {Boolean} Whether the animation is still running */ isRunning: function isRunning(id) { return running[id] != null; }, /** * Start the animation. * * @param stepCallback {Function} Pointer to function which is executed on every step. * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` * @param verifyCallback {Function} Executed before every animation step. * Signature of the method should be `function() { return continueWithAnimation; }` * @param completedCallback {Function} * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` * @param duration {Integer} Milliseconds to run the animation * @param easingMethod {Function} Pointer to easing function * Signature of the method should be `function(percent) { return modifiedValue; }` * @param root {Element ? document.body} Render root, when available. Used for internal * usage of requestAnimationFrame. * @return {Integer} Identifier of animation. Can be used to stop it any time. */ start: function start(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { var start = time(); var lastFrame = start; var percent = 0; var dropCounter = 0; var id = counter++; if (!root) { root = document.body; } // Compacting running db automatically every few new animations if (id % 20 === 0) { var newRunning = {}; for (var usedId in running) { newRunning[usedId] = true; } running = newRunning; } // This is the internal step method which is called every few milliseconds var step = function step(virtual) { // Normalize virtual value var render = virtual !== true; // Get current time var now = time(); // Verification is executed before next animation step if (!running[id] || verifyCallback && !verifyCallback(id)) { running[id] = null; completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false); return; } // For the current rendering to apply let's update omitted steps in memory. // This is important to bring internal state variables up-to-date with progress in time. if (render) { var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; for (var j = 0; j < Math.min(droppedFrames, 4); j++) { step(true); dropCounter++; } } // Compute percent value if (duration) { percent = (now - start) / duration; if (percent > 1) { percent = 1; } } // Execute step callback, then... var value = easingMethod ? easingMethod(percent) : percent; if ((stepCallback(value, now, render) === false || percent === 1) && render) { running[id] = null; completedCallback && completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, percent === 1 || duration == null); } else if (render) { lastFrame = now; core.effect.Animate.requestAnimationFrame(step, root); } }; // Mark as running running[id] = true; // Init first step core.effect.Animate.requestAnimationFrame(step, root); // Return unique animation ID return id; } }; var vsInstances = {}; function refreshAll() { for (var vs in vsInstances) { vsInstances[vs].refresh(); } } 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 goScrolling(elm, deltaX, deltaY, speed, easing, scrollingComplete) { var startLocationY = elm['scrollTop']; var startLocationX = elm['scrollLeft']; var positionX = startLocationX; var positionY = startLocationY; /** * keep the limit of scroll delta. */ /* istanbul ignore next */ if (startLocationY + deltaY < 0) { deltaY = -startLocationY; } var scrollHeight = elm['scrollHeight']; if (startLocationY + deltaY > scrollHeight) { deltaY = scrollHeight - startLocationY; } if (startLocationX + deltaX < 0) { deltaX = -startLocationX; } if (startLocationX + deltaX > elm['scrollWidth']) { deltaX = elm['scrollWidth'] - startLocationX; } var easingMethod = createEasingFunction(easing, easingPattern); var stepCallback = function stepCallback(percentage) { positionX = startLocationX + deltaX * percentage; positionY = startLocationY + deltaY * percentage; elm['scrollTop'] = Math.floor(positionY); elm['scrollLeft'] = Math.floor(positionX); }; var verifyCallback = function verifyCallback() { return Math.abs(positionY - startLocationY) <= Math.abs(deltaY) || Math.abs(positionX - startLocationX) <= Math.abs(deltaX); }; core.effect.Animate.start(stepCallback, verifyCallback, scrollingComplete, speed, easingMethod); } var api = { mounted: function mounted() { vsInstances[this._uid] = this; }, beforeDestroy: function beforeDestroy() { delete vsInstances[this._uid]; }, methods: { // public api scrollTo: function scrollTo(_ref) { var x = _ref.x, y = _ref.y; var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; if (typeof x === 'undefined') { x = this.vuescroll.state.internalScrollLeft || 0; } else { x = getNumericValue(x, this.scrollPanelElm.scrollWidth); } if (typeof y === 'undefined') { y = this.vuescroll.state.internalScrollTop || 0; } else { y = getNumericValue(y, this.scrollPanelElm.scrollHeight); } this.internalScrollTo(x, y, animate, force); }, scrollBy: function scrollBy(_ref2) { var _ref2$dx = _ref2.dx, dx = _ref2$dx === undefined ? 0 : _ref2$dx, _ref2$dy = _ref2.dy, dy = _ref2$dy === undefined ? 0 : _ref2$dy; var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var _vuescroll$state = this.vuescroll.state, _vuescroll$state$inte = _vuescroll$state.internalScrollLeft, internalScrollLeft = _vuescroll$state$inte === undefined ? 0 : _vuescroll$state$inte, _vuescroll$state$inte2 = _vuescroll$state.internalScrollTop, internalScrollTop = _vuescroll$state$inte2 === undefined ? 0 : _vuescroll$state$inte2; if (dx) { internalScrollLeft += getNumericValue(dx, this.scrollPanelElm.scrollWidth); } if (dy) { internalScrollTop += getNumericValue(dy, this.scrollPanelElm.scrollHeight); } this.internalScrollTo(internalScrollLeft, internalScrollTop, animate); }, zoomBy: function zoomBy(factor, animate, originLeft, originTop, callback) { if (this.mode != 'slide') { warn('zoomBy and zoomTo are only for slide mode!'); return; } this.scroller.zoomBy(factor, animate, originLeft, originTop, callback); }, zoomTo: function zoomTo(level) { var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var originLeft = arguments[2]; var originTop = arguments[3]; var callback = arguments[4]; if (this.mode != 'slide') { warn('zoomBy and zoomTo are only for slide mode!'); return; } this.scroller.zoomTo(level, animate, originLeft, originTop, callback); }, getCurrentPage: function getCurrentPage() { if (this.mode != 'slide' || !this.mergedOptions.vuescroll.paging) { warn('getCurrentPage and goToPage are only for slide mode and paging is enble!'); return; } return this.scroller.getCurrentPage(); }, goToPage: function goToPage(dest) { var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (this.mode != 'slide' || !this.mergedOptions.vuescroll.paging) { warn('getCurrentPage and goToPage are only for slide mode and paging is enble!'); return; } this.scroller.goToPage(dest, animate); }, triggerRefreshOrLoad: function triggerRefreshOrLoad(type) { if (this.mode != 'slide') { warn('You can only use triggerRefreshOrLoad in slide mode!'); return; } var isRefresh = this.mergedOptions.vuescroll.pullRefresh.enable; var isLoad = this.mergedOptions.vuescroll.pushLoad.enable; if (type == 'refresh' && !isRefresh) { warn('refresh must be enabled!'); return; } else if (type == 'load' && !isLoad) { warn('load must be enabled!'); return; } else if (type !== 'refresh' && type !== 'load') { warn('param must be one of load and refresh!'); return; } /* istanbul ignore if */ if (this.vuescroll.state[type + 'Stage'] == 'start') { return; } this.scroller.triggerRefreshOrLoad(type); return true; }, getCurrentviewDom: function getCurrentviewDom() { var _this = this; var parent = this.mode == 'slide' || this.mode == 'pure-native' ? this.scrollPanelElm : this.scrollContentElm; var children = parent.children; var domFragment = []; var isCurrentview = function isCurrentview(dom) { var _dom$getBoundingClien = dom.getBoundingClientRect(), left = _dom$getBoundingClien.left, top = _dom$getBoundingClien.top, width = _dom$getBoundingClien.width, height = _dom$getBoundingClien.height; var _$el$getBoundingClien = _this.$el.getBoundingClientRect(), parentLeft = _$el$getBoundingClien.left, parentTop = _$el$getBoundingClien.top, parentHeight = _$el$getBoundingClien.height, parentWidth = _$el$getBoundingClien.width; if (left - parentLeft + width > 0 && left - parentLeft < parentWidth && top - parentTop + height > 0 && top - parentTop < parentHeight) { return true; } return false; }; for (var i = 0; i < children.length; i++) { var dom = children.item(i); if (isCurrentview(dom) && !dom.isResizeElm) { domFragment.push(dom); } } return domFragment; }, // private api internalScrollTo: function internalScrollTo(destX, destY, animate, force) { var _this2 = this; if (this.mode == 'native' || this.mode == 'pure-native') { if (animate) { // hadnle for scroll complete var scrollingComplete = function scrollingComplete() { _this2.updateBarStateAndEmitEvent('handle-scroll-complete'); }; goScrolling(this.$refs['scrollPanel'].$el, destX - this.$refs['scrollPanel'].$el.scrollLeft, destY - this.$refs['scrollPanel'].$el.scrollTop, this.mergedOptions.scrollPanel.speed, this.mergedOptions.scrollPanel.easing, scrollingComplete); } else { this.$refs['scrollPanel'].$el.scrollTop = destY; this.$refs['scrollPanel'].$el.scrollLeft = destX; } } // for non-native we use scroller's scorllTo else if (this.mode == 'slide') { this.scroller.scrollTo(destX, destY, animate, undefined, force); } }, 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$getBoundingClien2 = this.$el.getBoundingClientRect(), left = _$el$getBoundingClien2.left, top = _$el$getBoundingClien2.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(); } } }; var nativeMode = { methods: { updateNativeModeBarState: function updateNativeModeBarState() { var scrollPanel = this.scrollPanelElm; var vuescroll = this.$el; var isPercent = this.mergedOptions.vuescroll.sizeStrategy == 'percent'; var clientWidth = isPercent ? vuescroll.clientWidth : extractNumberFromPx(this.vuescroll.state.width); var clientHeight = isPercent ? vuescroll.clientHeight : extractNumberFromPx(this.vuescroll.state.height); var heightPercentage = clientHeight * 100 / scrollPanel.scrollHeight; var widthPercentage = clientWidth * 100 / scrollPanel.scrollWidth; this.bar.vBar.state.posValue = scrollPanel.scrollTop * 100 / clientHeight; this.bar.hBar.state.posValue = scrollPanel.scrollLeft * 100 / clientWidth; this.bar.vBar.state.size = heightPercentage < 100 ? heightPercentage + '%' : 0; this.bar.hBar.state.size = widthPercentage < 100 ? widthPercentage + '%' : 0; } } }; /* * Scroller * http://github.com/zynga/scroller * * modified by wangyi7099 * * Copyright 2011, Zynga Inc. * Licensed under the MIT License. * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt * * Based on the work of: Unify Project (unify-project.org) * http://unify-project.org * Copyright 2011, Deutsche Telekom AG * License: MIT + Apache (V2) */ var animatingMethod = null; var noAnimatingMethod = null; function Scroller(callback, options) { this.__callback = callback; this.options = { /** Enable scrolling on x-axis */ scrollingX: true, /** Enable scrolling on y-axis */ scrollingY: true, /** Enable animations for deceleration, snap back, zooming and scrolling */ animating: true, /** duration for animations triggered by scrollTo/zoomTo */ animationDuration: 250, /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ bouncing: true, /** Enable locking to the main axis if user moves only slightly on one of them at start */ locking: true, /** Enable pagination mode (switching between full page content panes) */ paging: false, /** Enable snapping of content to a configured pixel grid */ snapping: false, /** Enable zooming of content via API, fingers and mouse wheel */ zooming: false, /** Minimum zoom level */ minZoom: 0.5, /** Maximum zoom level */ maxZoom: 3, /** Multiply or decrease scrolling speed **/ speedMultiplier: 1, /** Callback that is fired on the later of touch end or deceleration end, provided that another scrolling action has not begun. Used to know when to fade out a scrollbar. */ scrollingComplete: NOOP, animatingEasing: 'easeOutCubic', noAnimatingEasing: 'easeInOutCubic', /** This configures the amount of change applied to deceleration when reaching boundaries **/ penetrationDeceleration: 0.03, /** This configures the amount of change applied to acceleration when reaching boundaries **/ penetrationAcceleration: 0.08 }; for (var key in options) { this.options[key] = options[key]; } animatingMethod = createEasingFunction(this.options.animatingEasing, easingPattern); noAnimatingMethod = createEasingFunction(this.options.noAnimatingEasing, easingPattern); } var members = { /* --------------------------------------------------------------------------- INTERNAL FIELDS :: STATUS --------------------------------------------------------------------------- */ /** {Boolean} Whether only a single finger is used in touch handling */ __isSingleTouch: false, /** {Boolean} Whether a touch event sequence is in progress */ __isTracking: false, /** {Boolean} Whether a deceleration animation went to completion. */ __didDecelerationComplete: false, /** * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when * a gesturestart event happens. This has higher priority than dragging. */ __isGesturing: false, /** * {Boolean} Whether the user has moved by such a distance that we have enabled * dragging mode. Hint: It's only enabled after some pixels of movement to * not interrupt with clicks etc. */ __isDragging: false, /** * {Boolean} Not touching and dragging anymore, and smoothly animating the * touch sequence using deceleration. */ __isDecelerating: false, /** * {Boolean} Smoothly animating the currently configured change */ __isAnimating: false, /* --------------------------------------------------------------------------- INTERNAL FIELDS :: DIMENSIONS --------------------------------------------------------------------------- */ /** {Integer} Available outer left position (from document perspective) */ __clientLeft: 0, /** {Integer} Available outer top position (from document perspective) */ __clientTop: 0, /** {Integer} Available outer width */ __clientWidth: 0, /** {Integer} Available outer height */ __clientHeight: 0, /** {Integer} Outer width of content */ __contentWidth: 0, /** {Integer} Outer height of content */ __contentHeight: 0, /** {Integer} Snapping width for content */ __snapWidth: 100, /** {Integer} Snapping height for content */ __snapHeight: 100, /** {Integer} Height to assign to refresh area */ __refreshHeight: null, /** {Integer} Height to assign to refresh area */ __loadHeight: null, /** {Boolean} Whether the refresh process is enabled when the event is released now */ __refreshActive: false, /** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */ __refreshActivate: null, __refreshBeforeDeactivate: null, /** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */ __refreshDeactivate: null, /** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */ __refreshStart: null, __loadActive: null, __loadActivate: null, __loadBeforeDeactivate: null, __loadDeactivate: null, __loadStart: null, /** {Number} Zoom level */ __zoomLevel: 1, /** {Number} Scroll position on x-axis */ __scrollLeft: 0, /** {Number} Scroll position on y-axis */ __scrollTop: 0, /** {Integer} Maximum allowed scroll position on x-axis */ __maxScrollLeft: 0, /** {Integer} Maximum allowed scroll position on y-axis */ __maxScrollTop: 0, /* {Number} Scheduled left position (final position when animating) */ __scheduledLeft: 0, /* {Number} Scheduled top position (final position when animating) */ __scheduledTop: 0, /* {Number} Scheduled zoom level (final scale when animating) */ __scheduledZoom: 0, /** * current page */ __currentPageX: null, __currentPageY: null, /** * total page */ __totalXPage: null, __totalYPage: null, /* --------------------------------------------------------------------------- INTERNAL FIELDS :: LAST POSITIONS --------------------------------------------------------------------------- */ /** whether the scroller is disabled or not */ __disable: false, /** {Number} Left position of finger at start */ __lastTouchLeft: null, /** {Number} Top position of finger at start */ __lastTouchTop: null, /** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */ __lastTouchMove: null, /** {Array} List of positions, uses three indexes for each state: left, top, timestamp */ __positions: null, /* --------------------------------------------------------------------------- INTERNAL FIELDS :: DECELERATION SUPPORT --------------------------------------------------------------------------- */ /** {Integer} Minimum left scroll position during deceleration */ __minDecelerationScrollLeft: null, /** {Integer} Minimum top scroll position during deceleration */ __minDecelerationScrollTop: null, /** {Integer} Maximum left scroll position during deceleration */ __maxDecelerationScrollLeft: null, /** {Integer} Maximum top scroll position during deceleration */ __maxDecelerationScrollTop: null, /** {Number} Current factor to modify horizontal scroll position with on every step */ __decelerationVelocityX: null, /** {Number} Current factor to modify vertical scroll position with on every step */ __decelerationVelocityY: null, /* --------------------------------------------------------------------------- PUBLIC API --------------------------------------------------------------------------- */ /** * Configures the dimensions of the client (outer) and content (inner) elements. * Requires the available space for the outer element and the outer size of the inner element. * All values which are falsy (null or zero etc.) are ignored and the old value is kept. * * @param clientWidth {Integer ? null} Inner width of outer element * @param clientHeight {Integer ? null} Inner height of outer element * @param contentWidth {Integer ? null} Outer width of inner element * @param contentHeight {Integer ? null} Outer height of inner element */ setDimensions: function setDimensions(clientWidth, clientHeight, contentWidth, contentHeight) { var animate = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : trye; var self = this; // Only update values which are defined if (clientWidth === +clientWidth) { self.__clientWidth = clientWidth; } if (clientHeight === +clientHeight) { self.__clientHeight = clientHeight; } if (contentWidth === +contentWidth) { self.__contentWidth = contentWidth; } if (contentHeight === +contentHeight) { self.__contentHeight = contentHeight; } // Refresh maximums self.__computeScrollMax(); // Refresh scroll position self.scrollTo(self.__scrollLeft, self.__scrollTop, animate); }, /** * Sets the client coordinates in relation to the document. * * @param left {Integer ? 0} Left position of outer element * @param top {Integer ? 0} Top position of outer element */ setPosition: function setPosition(left, top) { var self = this; self.__clientLeft = left || 0; self.__clientTop = top || 0; }, /** * Configures the snapping (when snapping is active) * * @param width {Integer} Snapping width * @param height {Integer} Snapping height */ setSnapSize: function setSnapSize(width, height) { var self = this; self.__snapWidth = width; self.__snapHeight = height; }, /** * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever * the user event is released during visibility of this zone. This was introduced by some apps on iOS like * the official Twitter client. * * @param height {Integer} Height of pull-to-refresh zone on top of rendered list * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release. * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled. * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh. */ activatePullToRefresh: function activatePullToRefresh(height, _ref) { var activateCallback = _ref.activateCallback, deactivateCallback = _ref.deactivateCallback, startCallback = _ref.startCallback, beforeDeactivateCallback = _ref.beforeDeactivateCallback; var self = this; self.__refreshHeight = height; self.__refreshActivate = activateCallback; self.__refreshBeforeDeactivate = beforeDeactivateCallback; self.__refreshDeactivate = deactivateCallback; self.__refreshStart = startCallback; }, activatePushToLoad: function activatePushToLoad(height, _ref2) { var activateCallback = _ref2.activateCallback, deactivateCallback = _ref2.deactivateCallback, startCallback = _ref2.startCallback, beforeDeactivateCallback = _ref2.beforeDeactivateCallback; var self = this; self.__loadHeight = height; self.__loadActivate = activateCallback; self.__loadBeforeDeactivate = beforeDeactivateCallback; self.__loadDeactivate = deactivateCallback; self.__loadStart = startCallback; }, /** * Starts pull-to-refresh manually. */ triggerRefreshOrLoad: function triggerRefreshOrLoad() { var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'refresh'; var wasDecelerating = this.__isDecelerating; if (wasDecelerating) { core.effect.Animate.stop(wasDecelerating); this.__isDecelerating = false; } // Use publish instead of scrollTo to allow scrolling to out of boundary position // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled if (type == 'refresh') { this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true); if (this.__refreshStart) { this.__refreshStart(); this.__refreshActive = true; } } else { this.__publish(this.__scrollLeft, this.__maxScrollTop + this.__loadHeight, this.__zoomLevel, true); if (this.__loadStart) { this.__loadStart(); this.__loadActive = true; } } }, /** * Signalizes that pull-to-refresh is finished. */ finishRefreshOrLoad: function finishRefreshOrLoad() { var self = this; if (self.__refreshBeforeDeactivate && self.__refreshActive) { self.__refreshActive = false; self.__refreshBeforeDeactivate(function () { if (self.__refreshDeactivate) { self.__refreshDeactivate(); } self.scrollTo(self.__scrollLeft, self.__scrollTop, true); }); } else if (self.__refreshDeactivate && self.__refreshActive) { self.__refreshActive = false; self.__refreshDeactivate(); self.scrollTo(self.__scrollLeft, self.__scrollTop, true); } if (self.__loadBeforeDeactivate && self.__loadActive) { self.__loadActive = false; self.__loadBeforeDeactivate(function () { if (self.__loadDeactivate) { self.__loadDeactivate(); } self.scrollTo(self.__scrollLeft, self.__scrollTop, true); }); } else if (self.__loadDeactivate && self.__loadActive) { self.__loadActive = false; self.__loadDeactivate(); self.scrollTo(self.__scrollLeft, self.__scrollTop, true); } }, /** * Returns the scroll position and zooming values * * @return {Map} `left` and `top` scroll position and `zoom` level */ getValues: function getValues() { var self = this; return { left: self.__scrollLeft, top: self.__scrollTop, zoom: self.__zoomLevel }; }, /** * Returns the maximum scroll values * * @return {Map} `left` and `top` maximum scroll values */ getScrollMax: function getScrollMax() { var self = this; return { left: self.__maxScrollLeft, top: self.__maxScrollTop }; }, /** * Zooms to the given level. Supports optional animation. Zooms * the center when no coordinates are given. * * @param level {Number} Level to zoom to * @param animate {Boolean ? false} Whether to use animation * @param originLeft {Number ? null} Zoom in at given left coordinate * @param originTop {Number ? null} Zoom in at given top coordinate * @param callback {Function ? null} A callback that gets fired when the zoom is complete. */ zoomTo: function zoomTo(level, animate, originLeft, originTop, callback) { var self = this; if (!self.options.zooming) { throw new Error('Zooming is not enabled!'); } // Add callback if exists if (callback) { self.__zoomComplete = callback; } // Stop deceleration if (self.__isDecelerating) { core.effect.Animate.stop(self.__isDecelerating); self.__isDecelerating = false; } var oldLevel = self.__zoomLevel; // Normalize input origin to center of viewport if not defined if (originLeft == null) { originLeft = self.__clientWidth / 2; } if (originTop == null) { originTop = self.__clientHeight / 2; } // Limit level according to configuration level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); // Recompute maximum values while temporary tweaking maximum scroll ranges self.__computeScrollMax(level); // Recompute left and top coordinates based on new zoom level var left = (originLeft + self.__scrollLeft) * level / oldLevel - originLeft; var top = (originTop + self.__scrollTop) * level / oldLevel - originTop; // Limit x-axis if (left > self.__maxScrollLeft) { left = self.__maxScrollLeft; } else if (left < 0) { left = 0; } // Limit y-axis if (top > self.__maxScrollTop) { top = self.__maxScrollTop; } else if (top < 0) { top = 0; } // Push values out self.__publish(left, top, level, animate); }, /** * Zooms the content by the given factor. * * @param factor {Number} Zoom by given factor * @param animate {Boolean ? false} Whether to use animation * @param originLeft {Number ? 0} Zoom in at given left coordinate * @param originTop {Number ? 0} Zoom in at given top coordinate * @param callback {Function ? null} A callback that gets fired when the zoom is complete. */ zoomBy: function zoomBy(factor, animate, originLeft, originTop, callback) { var self = this; self.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop, callback); }, /** * Scrolls to the given position. Respect limitations and snapping automatically. * * @param left {Number?null} Horizontal scroll position, keeps current if value is <code>null</code> * @param top {Number?null} Vertical scroll position, keeps current if value is <code>null</code> * @param animate {Boolean?false} Whether the scrolling should happen using an animation * @param zoom {Number?null} Zoom level to go to */ scrollTo: function scrollTo(left, top, animate, zoom, force) { var self = this; // Stop deceleration if (self.__isDecelerating) { core.effect.Animate.stop(self.__isDecelerating); self.__isDecelerating = false; } // Correct coordinates based on new zoom level if (zoom != null && zoom !== self.__zoomLevel) { if (!self.options.zooming) { throw new Error('Zooming is not enabled!'); } left *= zoom; top *= zoom; // Recompute maximum values while temporary tweaking maximum scroll ranges