vuescroll
Version:
A beautiful scrollbar based on Vue.js for PC and mobile.
1,545 lines (1,324 loc) • 123 kB
JavaScript
/*
* 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