UNPKG

vue-drapload

Version:

one vue plug for resolve pull-to-refresh and load more data

512 lines (470 loc) 14.9 kB
/** * Created by stone on 2016/6/3. * Email:258137678@qq.com */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : (factory((global.Drapload = global.Drapload || {}))) }(this, function (exports) { 'use strict' var throttle = function (fn, delay) { var now, lastExec, timer, context, args var execute = function execute () { fn.apply(context, args) lastExec = now } return function () { context = this args = arguments now = Date.now() if (timer) { clearTimeout(timer) timer = null } if (lastExec) { var diff = delay - (now - lastExec) if (diff < 0) { execute() } else { timer = setTimeout(function () { execute() }, diff) } } else { execute() } } } /** * 获取滚动位置信息 * @param element * @returns {*} */ var getScrollTop = function (element) { if (element) { return element.scrollTop } else { return document.documentElement.scrollTop } } /** * 获取可视区域高度 * @returns {number} */ var getVisibleHeight = function (element) { if (element) { return element.offsetHeight } else { return document.documentElement.offsetHeight } } /** * 获取滚动区域高度信息 * @returns {number} */ var getScrollHeight = function (element) { if (element) { return element.scrollHeight } else { return document.documentElement.scrollHeight } } /** * 判断该元素是否已经加入dom树 * @param element * @returns {boolean} */ var isAttached = function (element) { var currentNode = element.parentNode while (currentNode) { if (currentNode.tagName === 'HTML') { return true } if (currentNode.nodeType === 11) { return false } currentNode = currentNode.parentNode } return false } // touches function fnTouches (e) { if (!e.touches) { e.touches = e.originalEvent.touches } } // touchstart function fnTouchstart (e) { var me = this me._startY = e.touches[ 0 ].pageY // 记住触摸时的scrolltop值 me.touchScrollTop = getScrollTop(me.element) } // touchmove function fnTouchmove (e) { var me = this var options = me._options var domUp = options.domUp me._curY = e.touches[ 0 ].pageY me._moveY = me._curY - me._startY if (me._moveY > 0) { me.direction = 'up' } else if (me._moveY < 0) { me.direction = 'down' } var _absMoveY = Math.abs(me._moveY) // 加载上方 if (options.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'up' ) { e.preventDefault() fnTransition(domUp.dom, 0) if (_absMoveY <= domUp.getDistance()) { // 下拉 } else if (_absMoveY > domUp.getDistance() && _absMoveY <= domUp.getDistance() * 2) { // 指定距离 < 下拉距离 < 指定距离*2 _absMoveY = domUp.getDistance() + (_absMoveY - domUp.getDistance()) * 0.5 } else { // 下拉距离 > 指定距离*2 _absMoveY = domUp.getDistance() + domUp.getDistance() * 0.5 + (_absMoveY - domUp.getDistance() * 2) * 0.2 } domUp.dom.style.height = _absMoveY + "px" domUp.pullingCall(_absMoveY) } } // touchend function fnTouchend () { var me = this var options = me._options var domUp = options.domUp var _absMoveY = Math.abs(me._moveY) //alert(me.touchScrollTop+me.direction) if (me.touchScrollTop <= 0 && me.direction == 'up' ) { fnTransition(options.domUp.dom, 300) if (_absMoveY > options.domUp.getDistance()) { domUp.dom.style.height = options.domUp.getDistance() + "px" domUp.loadingCall() me.loading = true me.directive.vm.$get(options.loadUpFn) } else { domUp.dom.style.height = '0px' } } me._moveY = 0 } var merge = function (org, aim) { for (var key in aim) { org[ key ] = aim[ key ] } } var Drapload = function (_directive) { var directive = _directive return { _options: {}, scrollTop: 0, _initConfig: function () { var me = this if (this.__initConfig) return this.__initConfig = true var element = directive.el me.directive = directive // 滑动区域 me.element = element // 滑动区域 me.isData = true me._options = { key: "scroll_" + parseInt(Math.random() * 10), domUp: { // 上方DOM dom: null, domClass: 'dropload-up', initialCall: function () {}, //初始化状态 pullingCall: function () {}, //下拉过程中 loadingCall: function () {},//加载中 loadEndCall: function () {},//加载完成 }, domDown: { // 下方DOM dom: null, domClass: 'dropload-down', initialCall: function () {},//初始化 loadingCall: function () {}, domNoData: function () {}, }, loadUpFn: '', // 上方function loadDownFn: '' // 下方function } merge(me._options.domUp, me.baseConfig.domUp) merge(me._options.domDown, me.baseConfig.domDown) //获取下拉刷新方法 var key = element.getAttribute('drapload-key') if (key) { directive.vm[ key ] = me me._options.key = key } //获取下拉刷新方法 me._options.loadUpFn = element.getAttribute('drapload-up') //获取下拉刷新方法 me._options.loadDownFn = element.getAttribute('drapload-down') }, _initDom: function () { var me = this if (this.__initDom) return this.__initDom = true var options = me._options var element = me.element var child = element.firstElementChild if (element.childElementCount != 1) { console.error("滚动元素最好只有一个孩子节点") } else { //绑定相关方法后才插入相关元素 if (options.loadUpFn) { options.domUp.dom = document.createElement('div') options.domUp.dom.setAttribute("class", options.domUp.domClass) options.domUp.initialCall() element.insertBefore(options.domUp.dom, child) options.domUp.getDistance = function () { return options.domUp.dom.firstElementChild.clientHeight } } if (options.loadDownFn) { options.domDown.dom = document.createElement('div') options.domDown.dom.setAttribute("class", options.domDown.domClass) options.domDown.initialCall() element.appendChild(options.domDown.dom) options.domDown.getDistance = function () { return options.domDown.dom.firstElementChild.clientHeight } } } }, _initEvent: function () { var me = this if (this.__initEvent) return this.__initEvent = true var element = me.element var options = me._options //绑定滚动事件 if (options.loadUpFn) { // 绑定触摸 me.touchstartCall = function (e) { if (!me.loading) { fnTouches.call(me, e) fnTouchstart.call(me, e) } } me.touchmoveCall = function (e) { if (!me.loading) { fnTouches.call(me, e) fnTouchmove.call(me, e) } } me.touchendCall = function () { if (!me.loading) { fnTouchend.call(me) } } element.addEventListener('touchmove', me.touchmoveCall) element.addEventListener('touchstart', me.touchstartCall) element.addEventListener('touchend', me.touchendCall) } if (options.loadDownFn) { me.scrollListener = throttle(me.doCheck.bind(me), 200) element.addEventListener('scroll', me.scrollListener) } //判断是否需要进入页面就开始检查一下数据是否需要加载。 var initializeExpr = element.getAttribute('drapload-initialize') var initialize = false if (initializeExpr) { initialize = Boolean(directive.vm.$get(initializeExpr)) } directive.initialize = initialize if (initialize) { me.doCheck() } }, /** * 控制加载更多是否需要 */ noData: function () { var me = this me.isData = false }, /** * 控制加载更多是否需要 */ hasData: function () { var me = this me.isData = true }, /** * 重置加载组件状态 * @param noData 确认 没有数据 */ resetload: function () { var me = this var options = me._options me.loading = false if (me.direction == 'up') { fnTransition(options.domUp.dom, 300) options.domUp.dom.style.height = '0px' options.domUp.loadEndCall() // 下拉刷新后,加载更多的数据状态需要更新 } else { } if (me.isData) { // 加载区修改样式 options.domDown.initialCall() } else { // 如果没数据 options.domDown.domNoData() } }, resetDataStatus: function () { var me = this me.isData = true }, doBind: function () { var me = this if (this.binded) return // eslint-disable-line this.binded = true //0:初始化配置参数 me._initConfig() //1:添加元素到容器内部 me._initDom() //2:绑定基础事件 me._initEvent() // 设置滚动元素 }, doCheck: function (force) { var me = this console.log("key----", me._options.key) var element = me.element var options = me._options me.scrollTop = getScrollTop(element) var downTrigger = getVisibleHeight(element) + me.scrollTop + 20 >= getScrollHeight(element) if (downTrigger && options.loadDownFn && me.isData && !me.loading) { options.domDown.loadingCall() me.loading = true directive.vm.$get(options.loadDownFn) } }, setScrollTop: function (top) { var me = this var element = me.element if (element) { element.scrollTop = top } }, bind: function (config) { var me = this var element = directive.el me.baseConfig = config || {} // 判断文档是否加载插入dom对象,一直尝试。直到 加入dom对象树种 directive.vm.$on('hook:ready', function () { if (isAttached(element)) { me.doBind() } }) // 再次显示的时候处理原来已经滚动到的位置 directive.vm.$on('hook:attached', function () { me.setScrollTop(me.scrollTop) }) this.bindTryCount = 0 var tryBind = function tryBind () { if (directive.bindTryCount > 10) return //eslint-disable-line directive.bindTryCount++ if (isAttached(element)) { me.doBind() } else { setTimeout(tryBind, 50) } } tryBind() }, unbind: function () { var me = this me.scrollListener && me.element.removeEventListener('scroll', me.scrollListener) me.touchstartCall && me.element.removeEventListener('touchstart', me.touchstartCall) me.touchmoveCall && me.element.removeEventListener('touchmove', me.touchmoveCall) me.touchendCall && me.element.removeEventListener('touchend', me.touchendCall) var key = me._options.key if (key) { delete directive.vm[ key ] } } } } // 基础配置信息,配置不同状态的显示情况 // css过渡 function fnTransition (dom, num) { if (dom.transNum != num) { dom.transNum = num dom.style.transition = 'all ' + num + 'ms' dom.style.webkitTransition = 'all ' + num + 'ms' } } var _config = { domUp: { // 上方DOM initialCall: function () { var me = this me.dom.innerHTML = '<div class="dropload-refresh">↓下拉刷新</div>' }, loadingCall: function () { var me = this me.dom.innerHTML = '<div class="dropload-load"><span class="loading"></span>加载中...</div>' }, loadEndCall: function () { var me = this me.dom.innerHTML = '<div class="dropload-load">加载完成</div>' }, pullingCall: function (_absMoveY) { var me = this if (_absMoveY <= me.getDistance()) { // 下拉 me.initialCall() } else if (_absMoveY > me.getDistance() && _absMoveY <= me.getDistance() * 2) { // 指定距离 < 下拉距离 < 指定距离*2 me.dom.innerHTML = '<div class="dropload-update">↑释放更新</div>' } } }, domDown: { // 下方DOM initialCall: function () { var me = this me.dom.innerHTML = '<div class="dropload-refresh">加载更多</div>' }, loadingCall: function () { var me = this me.dom.innerHTML = '<div class="dropload-load"><span class="loading"></span>加载中...</div>' }, domNoData: function () { var me = this me.dom.innerHTML = '' }, }, } function install (Vue, config) { config = config || {} config.domUp && merge(_config.domUp, config.domUp) config.domDown && merge(_config.domDown, config.domDown) Vue.directive('Drapload', { bind: function () { var me = this me.scroll = new Drapload(me) me.scroll.bind(_config) }, unbind: function () { var me = this me.scroll.unbind() } }) } Drapload.install = install if (window.Vue) { window.Drapload = Drapload Vue.use(install) } exports.install = install exports.Drapload = Drapload }))