index.vue 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. <template>
  2. <div class="infinite-list">
  3. <div class="infinite-list__container">
  4. <slot />
  5. </div>
  6. <div ref="indicator" class="infinite-list__indicator">
  7. <i v-if="loading" class="loading" />
  8. <span v-else-if="!hasMore" class="nomore">THE END</span>
  9. </div>
  10. </div>
  11. </template>
  12. <script setup lang="ts">
  13. import { ref, watchEffect } from 'vue'
  14. const props = withDefaults(
  15. defineProps<{
  16. loading: boolean
  17. hasMore: boolean
  18. threshold?: number
  19. scrollTarget?: string
  20. }>(),
  21. {
  22. threshold: 150,
  23. }
  24. )
  25. const emit = defineEmits<{
  26. (e: 'loadmore'): void
  27. }>()
  28. const indicator = ref<HTMLElement>()
  29. watchEffect(onInvalidate => {
  30. if (!indicator.value) return
  31. const io = new IntersectionObserver(
  32. ([entry]) => {
  33. if (entry.isIntersecting && !props.loading && props.hasMore) {
  34. emit('loadmore')
  35. }
  36. },
  37. {
  38. root: props.scrollTarget
  39. ? document.querySelector(props.scrollTarget)
  40. : null,
  41. rootMargin: `0px 0px ${props.threshold}px 0px`,
  42. }
  43. )
  44. io.observe(indicator.value)
  45. onInvalidate(() => io.disconnect())
  46. })
  47. </script>
  48. <style lang="scss">
  49. .infinite-list {
  50. overflow: auto;
  51. &__indicator {
  52. margin: 20px 0;
  53. height: 48px;
  54. line-height: 48px;
  55. text-align: center;
  56. font-size: 24px;
  57. color: #999;
  58. }
  59. .loading {
  60. @include icon('@img/loading.svg', 48px);
  61. display: inline-block;
  62. vertical-align: top;
  63. animation: rotate 1s ease-in-out infinite;
  64. }
  65. .nomore {
  66. position: relative;
  67. &:after,
  68. &:before {
  69. content: '';
  70. position: absolute;
  71. top: 50%;
  72. transform: scale(0.5) translate(0, -50%);
  73. height: 1px;
  74. width: 40px;
  75. background-color: #e5e5e5;
  76. }
  77. &:before {
  78. left: -50px;
  79. }
  80. &:after {
  81. right: -50px;
  82. }
  83. }
  84. }
  85. </style>