UNPKG

5.39 kBJavaScriptView Raw
1import Vue from 'vue';
2const ctx = '@@InfiniteScroll';
3
4var throttle = function(fn, delay) {
5 var now, lastExec, timer, context, args; //eslint-disable-line
6
7 var execute = function() {
8 fn.apply(context, args);
9 lastExec = now;
10 };
11
12 return function() {
13 context = this;
14 args = arguments;
15
16 now = Date.now();
17
18 if (timer) {
19 clearTimeout(timer);
20 timer = null;
21 }
22
23 if (lastExec) {
24 var diff = delay - (now - lastExec);
25 if (diff < 0) {
26 execute();
27 } else {
28 timer = setTimeout(() => {
29 execute();
30 }, diff);
31 }
32 } else {
33 execute();
34 }
35 };
36};
37
38var getScrollTop = function(element) {
39 if (element === window) {
40 return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
41 }
42
43 return element.scrollTop;
44};
45
46var getComputedStyle = Vue.prototype.$isServer ? {} : document.defaultView.getComputedStyle;
47
48var getScrollEventTarget = function(element) {
49 var currentNode = element;
50 // bugfix, see http://w3help.org/zh-cn/causes/SD9013 and http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
51 while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
52 var overflowY = getComputedStyle(currentNode).overflowY;
53 if (overflowY === 'scroll' || overflowY === 'auto') {
54 return currentNode;
55 }
56 currentNode = currentNode.parentNode;
57 }
58 return window;
59};
60
61var getVisibleHeight = function(element) {
62 if (element === window) {
63 return document.documentElement.clientHeight;
64 }
65
66 return element.clientHeight;
67};
68
69var getElementTop = function(element) {
70 if (element === window) {
71 return getScrollTop(window);
72 }
73 return element.getBoundingClientRect().top + getScrollTop(window);
74};
75
76var isAttached = function(element) {
77 var currentNode = element.parentNode;
78 while (currentNode) {
79 if (currentNode.tagName === 'HTML') {
80 return true;
81 }
82 if (currentNode.nodeType === 11) {
83 return false;
84 }
85 currentNode = currentNode.parentNode;
86 }
87 return false;
88};
89
90var doBind = function() {
91 if (this.binded) return; // eslint-disable-line
92 this.binded = true;
93
94 var directive = this;
95 var element = directive.el;
96
97 directive.scrollEventTarget = getScrollEventTarget(element);
98 directive.scrollListener = throttle(doCheck.bind(directive), 200);
99 directive.scrollEventTarget.addEventListener('scroll', directive.scrollListener);
100
101 var disabledExpr = element.getAttribute('infinite-scroll-disabled');
102 var disabled = false;
103
104 if (disabledExpr) {
105 this.vm.$watch(disabledExpr, function(value) {
106 directive.disabled = value;
107 if (!value && directive.immediateCheck) {
108 doCheck.call(directive);
109 }
110 });
111 disabled = Boolean(directive.vm[disabledExpr]);
112 }
113 directive.disabled = disabled;
114
115 var distanceExpr = element.getAttribute('infinite-scroll-distance');
116 var distance = 0;
117 if (distanceExpr) {
118 distance = Number(directive.vm[distanceExpr] || distanceExpr);
119 if (isNaN(distance)) {
120 distance = 0;
121 }
122 }
123 directive.distance = distance;
124
125 var immediateCheckExpr = element.getAttribute('infinite-scroll-immediate-check');
126 var immediateCheck = true;
127 if (immediateCheckExpr) {
128 immediateCheck = Boolean(directive.vm[immediateCheckExpr]);
129 }
130 directive.immediateCheck = immediateCheck;
131
132 if (immediateCheck) {
133 doCheck.call(directive);
134 }
135
136 var eventName = element.getAttribute('infinite-scroll-listen-for-event');
137 if (eventName) {
138 directive.vm.$on(eventName, function() {
139 doCheck.call(directive);
140 });
141 }
142};
143
144var doCheck = function(force) {
145 var scrollEventTarget = this.scrollEventTarget;
146 var element = this.el;
147 var distance = this.distance;
148
149 if (force !== true && this.disabled) return; //eslint-disable-line
150 var viewportScrollTop = getScrollTop(scrollEventTarget);
151 var viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget);
152
153 var shouldTrigger = false;
154
155 if (scrollEventTarget === element) {
156 shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance;
157 } else {
158 var elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop;
159
160 shouldTrigger = viewportBottom + distance >= elementBottom;
161 }
162
163 if (shouldTrigger && this.expression) {
164 this.expression();
165 }
166};
167
168export default {
169 bind(el, binding, vnode) {
170 el[ctx] = {
171 el,
172 vm: vnode.context,
173 expression: binding.value
174 };
175 const args = arguments;
176 var cb = function() {
177 el[ctx].vm.$nextTick(function() {
178 if (isAttached(el)) {
179 doBind.call(el[ctx], args);
180 }
181
182 el[ctx].bindTryCount = 0;
183
184 var tryBind = function() {
185 if (el[ctx].bindTryCount > 10) return; //eslint-disable-line
186 el[ctx].bindTryCount++;
187 if (isAttached(el)) {
188 doBind.call(el[ctx], args);
189 } else {
190 setTimeout(tryBind, 50);
191 }
192 };
193
194 tryBind();
195 });
196 };
197 if (el[ctx].vm._isMounted) {
198 cb();
199 return;
200 }
201 el[ctx].vm.$on('hook:mounted', cb);
202 },
203
204 unbind(el) {
205 if (el[ctx] && el[ctx].scrollEventTarget) {
206 el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener);
207 }
208 }
209};