vuescroll
Version:
A beautiful scrollbar based on Vue.js for PC and mobile.
265 lines (251 loc) • 7.88 kB
JavaScript
import {
createEasingFunction,
easingPattern
} from '../third-party/easingPattern';
import { core } from '../third-party/scroller/animate';
import { warn, isChildInParent } from '../util';
const vsInstances = {};
export function refreshAll() {
for (let vs in vsInstances) {
vsInstances[vs].refresh();
}
}
function getNumericValue(distance, size) {
let number;
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) {
const startLocationY = elm['scrollTop'];
const startLocationX = elm['scrollLeft'];
let positionX = startLocationX;
let positionY = startLocationY;
/**
* keep the limit of scroll delta.
*/
/* istanbul ignore next */
if (startLocationY + deltaY < 0) {
deltaY = -startLocationY;
}
const 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;
}
const easingMethod = createEasingFunction(easing, easingPattern);
const stepCallback = percentage => {
positionX = startLocationX + deltaX * percentage;
positionY = startLocationY + deltaY * percentage;
elm['scrollTop'] = Math.floor(positionY);
elm['scrollLeft'] = Math.floor(positionX);
};
const 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
);
}
export default {
mounted() {
vsInstances[this._uid] = this;
},
beforeDestroy() {
delete vsInstances[this._uid];
},
methods: {
// public api
scrollTo({ x, y }, animate = true, force = 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({ dx = 0, dy = 0 }, animate = true) {
let {
internalScrollLeft = 0,
internalScrollTop = 0
} = this.vuescroll.state;
if (dx) {
internalScrollLeft += getNumericValue(
dx,
this.scrollPanelElm.scrollWidth
);
}
if (dy) {
internalScrollTop += getNumericValue(
dy,
this.scrollPanelElm.scrollHeight
);
}
this.internalScrollTo(internalScrollLeft, internalScrollTop, animate);
},
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(level, animate = false, originLeft, originTop, callback) {
if (this.mode != 'slide') {
warn('zoomBy and zoomTo are only for slide mode!');
return;
}
this.scroller.zoomTo(level, animate, originLeft, originTop, callback);
},
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(dest, animate = 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(type) {
if (this.mode != 'slide') {
warn('You can only use triggerRefreshOrLoad in slide mode!');
return;
}
const isRefresh = this.mergedOptions.vuescroll.pullRefresh.enable;
const 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() {
const parent =
this.mode == 'slide' || this.mode == 'pure-native'
? this.scrollPanelElm
: this.scrollContentElm;
const children = parent.children;
const domFragment = [];
const isCurrentview = dom => {
const { left, top, width, height } = dom.getBoundingClientRect();
const {
left: parentLeft,
top: parentTop,
height: parentHeight,
width: parentWidth
} = this.$el.getBoundingClientRect();
if (
left - parentLeft + width > 0 &&
left - parentLeft < parentWidth &&
top - parentTop + height > 0 &&
top - parentTop < parentHeight
) {
return true;
}
return false;
};
for (let i = 0; i < children.length; i++) {
const dom = children.item(i);
if (isCurrentview(dom) && !dom.isResizeElm) {
domFragment.push(dom);
}
}
return domFragment;
},
// private api
internalScrollTo(destX, destY, animate, force) {
if (this.mode == 'native' || this.mode == 'pure-native') {
if (animate) {
// hadnle for scroll complete
const scrollingComplete = () => {
this.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(elm, animate = true) {
const 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
const { left, top } = this.$el.getBoundingClientRect();
// child elm left, top
const { left: childLeft, top: childTop } = elm.getBoundingClientRect();
const diffX = left - childLeft;
const diffY = top - childTop;
this.scrollBy(
{
dx: -diffX,
dy: -diffY
},
animate
);
},
refresh() {
this.refreshInternalStatus();
}
}
};