vue-infinite-scroll.js 6.7 KB

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