infiniteloading.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <div class="nut-infiniteloading" ref="scroller" @touchstart="touchStartHandle($event)" @touchmove="touchMoveHandle($event)">
  3. <slot></slot>
  4. <div class="load-more">
  5. <div class="bottom-tips" v-if="isShowBottomTips">
  6. <template v-if="isLoading">
  7. <template v-if="!slotLoading"> <i class="loading-hint"></i><span class="loading-txt">加载中...</span> </template>
  8. <slot name="loading" v-else></slot>
  9. </template>
  10. <template v-else-if="!hasMore">
  11. <span class="tips-txt" v-if="!slotUnloadMore">{{ unloadMoreTxt }}</span>
  12. <slot name="unloadMore" v-else></slot>
  13. </template>
  14. </div>
  15. </div>
  16. </div>
  17. </template>
  18. <script>
  19. export default {
  20. name: 'nut-infiniteloading',
  21. props: {
  22. hasMore: {
  23. type: Boolean,
  24. default: true
  25. },
  26. isLoading: {
  27. type: Boolean,
  28. default: false
  29. },
  30. threshold: {
  31. type: Number,
  32. default: 200
  33. },
  34. useWindow: {
  35. type: Boolean,
  36. default: true
  37. },
  38. useCapture: {
  39. type: Boolean,
  40. default: false
  41. },
  42. isShowMod: {
  43. type: Boolean,
  44. default: false
  45. },
  46. isShowBottomTips: {
  47. type: Boolean,
  48. default: true
  49. },
  50. unloadMoreTxt: {
  51. type: String,
  52. default: '哎呀,这里是底部了啦'
  53. },
  54. scrollChange: {
  55. type: Function
  56. },
  57. containerId: {
  58. type: String,
  59. default: ''
  60. }
  61. },
  62. data() {
  63. return {
  64. startX: 0,
  65. startY: 0,
  66. diffX: 0,
  67. diffY: 0,
  68. beforeScrollTop: 0,
  69. slotUnloadMore: false,
  70. slotLoading: false
  71. };
  72. },
  73. mounted: function() {
  74. const parentElement = this.getParentElement(this.$el);
  75. let scrollEl = window;
  76. if (this.useWindow === false) {
  77. scrollEl = parentElement;
  78. }
  79. this.scrollEl = scrollEl;
  80. this.scrollListener();
  81. this.slotUnloadMore = this.$slots.unloadMore ? true : false;
  82. this.slotLoading = this.$slots.loading ? true : false;
  83. },
  84. methods: {
  85. touchStartHandle(e) {
  86. try {
  87. this.startX = Number(e.changedTouches[0].pageX);
  88. this.startY = Number(e.changedTouches[0].pageY);
  89. } catch (e) {
  90. console.log(e.message);
  91. }
  92. },
  93. touchMoveHandle(e) {
  94. let endX = Number(e.changedTouches[0].pageX);
  95. let endY = Number(e.changedTouches[0].pageY);
  96. this.diffX = endX - this.startX;
  97. this.diffY = endY - this.startY;
  98. },
  99. getParentElement(el) {
  100. if (this.containerId) {
  101. return document.querySelector(`#${this.containerId}`);
  102. }
  103. return el && el.parentNode;
  104. },
  105. scrollListener() {
  106. this.scrollEl.addEventListener('scroll', this.handleScroll, this.useCapture);
  107. window.addEventListener('resize', this.handleScroll, this.useCapture);
  108. },
  109. requestAniFrame() {
  110. return (
  111. window.requestAnimationFrame ||
  112. window.webkitRequestAnimationFrame ||
  113. window.mozRequestAnimationFrame ||
  114. function(callback) {
  115. window.setTimeout(callback, 1000 / 60);
  116. }
  117. );
  118. },
  119. handleScroll() {
  120. this.requestAniFrame()(() => {
  121. if (!this.isScrollAtBottom() || !this.hasMore || this.isLoading || !this.isShowMod) {
  122. return false;
  123. } else {
  124. this.$emit('loadmore');
  125. }
  126. });
  127. },
  128. calculateTopPosition(el) {
  129. if (!el) {
  130. return 0;
  131. }
  132. return el.offsetTop + this.calculateTopPosition(el.offsetParent);
  133. },
  134. getWindowScrollTop() {
  135. return window.pageYOffset !== undefined
  136. ? window.pageYOffset
  137. : (document.documentElement || document.body.parentNode || document.body).scrollTop;
  138. },
  139. isScrollAtBottom() {
  140. let offsetDistance;
  141. let resScrollTop = 0;
  142. const windowScrollTop = this.getWindowScrollTop();
  143. if (this.useWindow) {
  144. offsetDistance = this.calculateTopPosition(this.$refs.scroller) + this.$refs.scroller.offsetHeight - windowScrollTop - window.innerHeight;
  145. } else {
  146. const { scrollHeight, clientHeight, scrollTop } = this.scrollEl;
  147. offsetDistance = scrollHeight - clientHeight - scrollTop;
  148. resScrollTop = scrollTop;
  149. }
  150. this.$emit('scrollChange', this.useWindow ? windowScrollTop : resScrollTop);
  151. // 保证是往下滑动的
  152. let beforeScrollTop = this.beforeScrollTop;
  153. this.beforeScrollTop = windowScrollTop;
  154. return offsetDistance <= this.threshold && windowScrollTop >= this.beforeScrollTop;
  155. }
  156. },
  157. activated() {
  158. if (this.keepAlive) {
  159. this.keepAlive = false;
  160. this.scrollListener();
  161. }
  162. },
  163. deactivated() {
  164. this.keepAlive = true;
  165. this.scrollEl.removeEventListener('scroll', this.handleScroll, this.useCapture);
  166. window.removeEventListener('resize', this.handleScroll, this.useCapture);
  167. },
  168. destroyed() {
  169. this.scrollEl.removeEventListener('scroll', this.handleScroll, this.useCapture);
  170. window.removeEventListener('resize', this.handleScroll, this.useCapture);
  171. }
  172. };
  173. </script>