12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- <template>
- <div class="infinite-list">
- <div class="infinite-list__container">
- <slot />
- </div>
- <div ref="indicator" class="infinite-list__indicator">
- <i v-if="loading" class="loading" />
- <span v-else-if="!hasMore" class="nomore">THE END</span>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, watchEffect } from 'vue'
- const props = withDefaults(
- defineProps<{
- loading: boolean
- hasMore: boolean
- threshold?: number
- scrollTarget?: string
- }>(),
- {
- threshold: 150,
- }
- )
- const emit = defineEmits<{
- (e: 'loadmore'): void
- }>()
- const indicator = ref<HTMLElement>()
- watchEffect(onInvalidate => {
- if (!indicator.value) return
- const io = new IntersectionObserver(
- ([entry]) => {
- if (entry.isIntersecting && !props.loading && props.hasMore) {
- emit('loadmore')
- }
- },
- {
- root: props.scrollTarget
- ? document.querySelector(props.scrollTarget)
- : null,
- rootMargin: `0px 0px ${props.threshold}px 0px`,
- }
- )
- io.observe(indicator.value)
- onInvalidate(() => io.disconnect())
- })
- </script>
- <style lang="scss">
- .infinite-list {
- overflow: auto;
- &__indicator {
- margin: 20px 0;
- height: 48px;
- line-height: 48px;
- text-align: center;
- font-size: 24px;
- color: #999;
- }
- .loading {
- @include icon('@img/loading.svg', 48px);
- display: inline-block;
- vertical-align: top;
- animation: rotate 1s ease-in-out infinite;
- }
- .nomore {
- position: relative;
- &:after,
- &:before {
- content: '';
- position: absolute;
- top: 50%;
- transform: scale(0.5) translate(0, -50%);
- height: 1px;
- width: 40px;
- background-color: #e5e5e5;
- }
- &:before {
- left: -50px;
- }
- &:after {
- right: -50px;
- }
- }
- }
- </style>
|