infiniteloading.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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">{{loadingTxt}}</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. loadingTxt: {
  55. type: String,
  56. default: '加载中...'
  57. },
  58. scrollChange: {
  59. type: Function
  60. },
  61. containerId: {
  62. type: String,
  63. default: ''
  64. }
  65. },
  66. data() {
  67. return {
  68. startX: 0,
  69. startY: 0,
  70. diffX: 0,
  71. diffY: 0,
  72. beforeScrollTop: 0,
  73. slotUnloadMore: false,
  74. slotLoading: false
  75. };
  76. },
  77. mounted: function() {
  78. const parentElement = this.getParentElement(this.$el);
  79. let scrollEl = window;
  80. if (this.useWindow === false) {
  81. scrollEl = parentElement;
  82. }
  83. this.scrollEl = scrollEl;
  84. this.scrollListener();
  85. this.slotUnloadMore = this.$slots.unloadMore ? true : false;
  86. this.slotLoading = this.$slots.loading ? true : false;
  87. },
  88. methods: {
  89. touchStartHandle(e) {
  90. try {
  91. this.startX = Number(e.changedTouches[0].pageX);
  92. this.startY = Number(e.changedTouches[0].pageY);
  93. } catch (e) {
  94. console.log(e.message);
  95. }
  96. },
  97. touchMoveHandle(e) {
  98. let endX = Number(e.changedTouches[0].pageX);
  99. let endY = Number(e.changedTouches[0].pageY);
  100. this.diffX = endX - this.startX;
  101. this.diffY = endY - this.startY;
  102. },
  103. getParentElement(el) {
  104. if (this.containerId) {
  105. return document.querySelector(`#${this.containerId}`);
  106. }
  107. return el && el.parentNode;
  108. },
  109. scrollListener() {
  110. this.scrollEl.addEventListener('scroll', this.handleScroll, this.useCapture);
  111. window.addEventListener('resize', this.handleScroll, this.useCapture);
  112. },
  113. requestAniFrame() {
  114. return (
  115. window.requestAnimationFrame ||
  116. window.webkitRequestAnimationFrame ||
  117. window.mozRequestAnimationFrame ||
  118. function(callback) {
  119. window.setTimeout(callback, 1000 / 60);
  120. }
  121. );
  122. },
  123. handleScroll() {
  124. this.requestAniFrame()(() => {
  125. if (!this.isScrollAtBottom() || !this.hasMore || this.isLoading || !this.isShowMod) {
  126. return false;
  127. } else {
  128. this.$emit('loadmore');
  129. }
  130. });
  131. },
  132. calculateTopPosition(el) {
  133. if (!el) {
  134. return 0;
  135. }
  136. return el.offsetTop + this.calculateTopPosition(el.offsetParent);
  137. },
  138. getWindowScrollTop() {
  139. return window.pageYOffset !== undefined
  140. ? window.pageYOffset
  141. : (document.documentElement || document.body.parentNode || document.body).scrollTop;
  142. },
  143. isScrollAtBottom() {
  144. let offsetDistance;
  145. let resScrollTop = 0;
  146. const windowScrollTop = this.getWindowScrollTop();
  147. if (this.useWindow) {
  148. offsetDistance = this.calculateTopPosition(this.$refs.scroller) + this.$refs.scroller.offsetHeight - windowScrollTop - window.innerHeight;
  149. } else {
  150. const { scrollHeight, clientHeight, scrollTop } = this.scrollEl;
  151. offsetDistance = scrollHeight - clientHeight - scrollTop;
  152. resScrollTop = scrollTop;
  153. }
  154. this.$emit('scrollChange', this.useWindow ? windowScrollTop : resScrollTop);
  155. // 保证是往下滑动的
  156. let beforeScrollTop = this.beforeScrollTop;
  157. this.beforeScrollTop = windowScrollTop;
  158. return offsetDistance <= this.threshold && windowScrollTop >= this.beforeScrollTop;
  159. }
  160. },
  161. activated() {
  162. if (this.keepAlive) {
  163. this.keepAlive = false;
  164. this.scrollListener();
  165. }
  166. },
  167. deactivated() {
  168. this.keepAlive = true;
  169. this.scrollEl.removeEventListener('scroll', this.handleScroll, this.useCapture);
  170. window.removeEventListener('resize', this.handleScroll, this.useCapture);
  171. },
  172. destroyed() {
  173. this.scrollEl.removeEventListener('scroll', this.handleScroll, this.useCapture);
  174. window.removeEventListener('resize', this.handleScroll, this.useCapture);
  175. }
  176. };
  177. </script>