UNPKG

vuescroll

Version:

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

373 lines (339 loc) 11.6 kB
/** * These mixes is exclusive for slide mode */ import Scroller from 'core/third-party/scroller/index'; import { render } from 'core/third-party/scroller/render'; import { listenContainer } from 'core/third-party/scroller/listener'; import { __REFRESH_DOM_NAME, __LOAD_DOM_NAME } from 'shared/constants'; import { createSlideModeStyle } from 'shared/util'; createSlideModeStyle(); /** * @description refresh and load callback */ function createStateCallbacks(type, stageType, vm, tipDom) { const listeners = vm.$listeners; const activateCallback = () => { vm.vuescroll.state[stageType] = 'active'; vm.$emit(type + '-activate', vm, tipDom); }; const deactivateCallback = () => { vm.vuescroll.state[stageType] = 'deactive'; vm.$emit(type + '-deactivate', vm, tipDom); }; const beforeDeactiveEnd = () => { vm.vuescroll.state[stageType] = 'beforeDeactiveEnd'; vm.$emit(type + '-before-deactivate-end', vm, tipDom); }; let startCallback = () => { vm.vuescroll.state[stageType] = 'start'; setTimeout(() => { vm.scroller.finishRefreshOrLoad(); }, 2000); // Default start stage duration }; // let beforeDeactivateCallback = done => { // vm.vuescroll.state[stageType] = 'beforeDeactive'; // setTimeout(function() { // done(); // }, 500); // Default before-deactivated stage duration // }; let beforeDeactivateCallback; /* istanbul ignore if */ if (listeners[type + '-before-deactivate']) { beforeDeactivateCallback = (done) => { vm.vuescroll.state[stageType] = 'beforeDeactive'; vm.$emit(type + '-before-deactivate', vm, tipDom, done.bind(vm.scroller)); }; } /* istanbul ignore if */ if (listeners[type + '-start']) { startCallback = () => { vm.vuescroll.state[stageType] = 'start'; vm.$emit( type + '-start', vm, tipDom, vm.scroller.finishRefreshOrLoad.bind(vm.scroller) ); }; } return { activateCallback, deactivateCallback, startCallback, beforeDeactivateCallback, beforeDeactiveEnd }; } export default { mounted() { this.vsMounted = true; }, computed: { pullRefreshTip() { return this.mergedOptions.vuescroll.pullRefresh.tips[ this.vuescroll.state.refreshStage ]; }, pushLoadTip() { return this.mergedOptions.vuescroll.pushLoad.tips[ this.vuescroll.state.loadStage ]; }, refreshLoad() { return ( this.mergedOptions.vuescroll.pullRefresh.enable || this.mergedOptions.vuescroll.pushLoad.enable ); }, refrehDomVisiable() { return this.vsMounted && this.outTheTopBoundary; }, loadDomVisiable() { return this.vsMounted && this.outTheBottomBoundary; } }, data() { return { vuescroll: { state: { /** Default tips of refresh and load */ refreshStage: 'deactive', loadStage: 'deactive' } }, vsMounted: false, outTheTopBoundary: false, outTheBottomBoundary: false }; }, methods: { // Update: // 1. update height/width // 2. update refresh or load updateScroller() { this.updateDimesion(); this.registryRefreshLoad(); }, updateDimesion() { const clientWidth = this.$el.clientWidth; const clientHeight = this.$el.clientHeight; let contentWidth = this.scrollPanelElm.scrollWidth; let contentHeight = this.scrollPanelElm.scrollHeight; let refreshHeight = 0; let loadHeight = 0; // If the refresh option is true,let's give a "margin-top" style to // the refresh-tip dom. let it to be invisible when doesn't trigger // refresh. if (this.mergedOptions.vuescroll.pullRefresh.enable) { if (this.vsMounted) { const refreshDom = this.$refs[__REFRESH_DOM_NAME].elm || this.$refs[__REFRESH_DOM_NAME]; refreshHeight = refreshDom.offsetHeight; refreshDom.style.marginTop = -refreshHeight + 'px'; } } if (this.mergedOptions.vuescroll.pushLoad.enable) { if (this.vsMounted) { const loadDom = this.$refs[__LOAD_DOM_NAME].elm || this.$refs[__LOAD_DOM_NAME]; loadHeight = loadDom.offsetHeight; contentHeight -= loadHeight; loadDom.style.bottom = `-${loadHeight}px`; } } if (this.scroller) { this.scroller.setDimensions( clientWidth, clientHeight, contentWidth, contentHeight, false ); } }, registryRefreshLoad() { // registry refresh if (this.mergedOptions.vuescroll.pullRefresh.enable) { this.registryEvent('refresh'); } // registry load if (this.mergedOptions.vuescroll.pushLoad.enable) { this.registryEvent('load'); } }, registryScroller({ left = 0, top = 0, zoom = 1 } = {}) { const { preventDefault, preventDefaultOnMove } = this.mergedOptions.vuescroll.scroller; let { paging, snapping: { enable: snapping }, renderMethod, zooming, locking } = this.mergedOptions.vuescroll; // disale zooming when refresh or load enabled zooming = !this.refreshLoad && !paging && !snapping && zooming; const { scrollingY, scrollingX } = this.mergedOptions.scrollPanel; const scrollingComplete = this.scrollingComplete.bind(this); // Initialize Scroller this.scroller = new Scroller( render(this.scrollPanelElm, window, 'px', renderMethod), { ...this.mergedOptions.vuescroll.scroller, zooming, scrollingY, scrollingX: scrollingX && !this.refreshLoad, animationDuration: this.mergedOptions.scrollPanel.speed, paging, snapping, scrollingComplete, locking } ); this.scroller.__disable = this.mergedOptions.vuescroll.scroller.disable; this.scroller.__scrollLeft = left; this.scroller.__scrollTop = top; this.scroller.__zoomLevel = zoom; // Set snap if (snapping) { this.scroller.setSnapSize( this.mergedOptions.vuescroll.snapping.width, this.mergedOptions.vuescroll.snapping.height ); } var rect = this.$el.getBoundingClientRect(); this.scroller.setPosition( rect.left + this.$el.clientLeft, rect.top + this.$el.clientTop ); // Get destroy callback const cb = listenContainer( this.$el, this.scroller, (eventType) => { // Thie is to dispatch the event from the scroller. // to let vuescroll refresh the dom switch (eventType) { case 'mousedown': this.vuescroll.state.isDragging = true; break; case 'onscroll': { /** * Trigger auto load */ const stage = this.vuescroll.state['loadStage']; const { enable, auto, autoLoadDistance } = this.mergedOptions.vuescroll.pushLoad; const { __scrollTop, __maxScrollTop } = this.scroller; if ( stage != 'start' && enable && auto && !this.lockAutoLoad && // auto load debounce autoLoadDistance >= __maxScrollTop - __scrollTop && __scrollTop > 0 ) { this.lockAutoLoad = true; this.triggerRefreshOrLoad('load'); } if (autoLoadDistance < __maxScrollTop - __scrollTop) { this.lockAutoLoad = false; } this.handleScroll(false); } break; case 'mouseup': this.vuescroll.state.isDragging = false; break; } }, zooming, preventDefault, preventDefaultOnMove ); this.updateScroller(); return cb; }, updateSlideModeBarState() { // update slide mode scrollbars' state let heightPercentage, widthPercentage; const vuescroll = this.$el; const scroller = this.scroller; let outerLeft = 0; let outerTop = 0; const { clientWidth, clientHeight } = this.$el; const contentWidth = clientWidth + this.scroller.__maxScrollLeft; const contentHeight = clientHeight + this.scroller.__maxScrollTop; // We should add the the height or width that is // out of horizontal bountry to the total length /* istanbul ignore if */ if (scroller.__scrollLeft < 0) { outerLeft = -scroller.__scrollLeft; } /* istanbul ignore next */ else if ( scroller.__scrollLeft > scroller.__maxScrollLeft ) { outerLeft = scroller.__scrollLeft - scroller.__maxScrollLeft; } // out of vertical bountry if (scroller.__scrollTop < 0) { outerTop = -scroller.__scrollTop; this.outTheBottomBoundary = false; this.outTheTopBoundary = true; } else if (scroller.__scrollTop > scroller.__maxScrollTop) { outerTop = scroller.__scrollTop - scroller.__maxScrollTop; this.outTheTopBoundary = false; this.outTheBottomBoundary = true; } else { this.outTheTopBoundary = this.outTheBottomBoundary = false; } heightPercentage = clientHeight / (contentHeight + outerTop); widthPercentage = clientWidth / (contentWidth + outerLeft); const scrollTop = Math.min( Math.max(0, scroller.__scrollTop), scroller.__maxScrollTop ); const scrollLeft = Math.min( Math.max(0, scroller.__scrollLeft), scroller.__maxScrollLeft ); this.bar.vBar.state.posValue = ((scrollTop + outerTop) * 100) / vuescroll.clientHeight; this.bar.hBar.state.posValue = ((scrollLeft + outerLeft) * 100) / vuescroll.clientWidth; /* istanbul ignore if */ if (scroller.__scrollLeft < 0) { this.bar.hBar.state.posValue = 0; } if (scroller.__scrollTop < 0) { this.bar.vBar.state.posValue = 0; } this.bar.vBar.state.size = heightPercentage < 1 ? heightPercentage : 0; this.bar.hBar.state.size = widthPercentage < 1 ? widthPercentage : 0; }, registryEvent(type) { const domName = type == 'refresh' ? __REFRESH_DOM_NAME : __LOAD_DOM_NAME; const activateFunc = type == 'refresh' ? this.scroller.activatePullToRefresh : this.scroller.activatePushToLoad; const stageType = type == 'refresh' ? 'refreshStage' : 'loadStage'; const tipDom = this.$refs[domName].elm || this.$refs[domName]; const cbs = createStateCallbacks(type, stageType, this, tipDom); const height = tipDom.offsetHeight; activateFunc.bind(this.scroller)(height, cbs); }, getSlidePosition() { return { scrollLeft: this.scroller.__scrollLeft, scrollTop: this.scroller.__scrollTop }; } } };