Files
vue-ts-example/src/components/UseIntersectionObserverInfiniteLoading.vue
严浩 5f98fe12ba
All checks were successful
/ build-and-deploy-to-vercel (push) Successful in 1m24s
/ playwright (push) Successful in 2m6s
/ depcheck (push) Successful in 1m38s
feat: 更新无限加载组件
2024-12-08 00:04:31 +08:00

118 lines
3.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
function checkIsVisible(el: Element, root: Element | null = null) {
if (!el) return false;
const elRect = el.getBoundingClientRect();
const rootRect = root
? root.getBoundingClientRect()
: { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };
return (
elRect.bottom >= rootRect.top &&
elRect.top <= rootRect.bottom &&
elRect.right >= rootRect.left &&
elRect.left <= rootRect.right
);
}
</script>
<script setup lang="ts">
const props = defineProps<{
asyncLoad: () => Promise<{ hasMore: boolean }>;
}>();
defineSlots<{
loading(): unknown;
complete(): unknown;
error(props: { retry: () => void }): unknown;
}>();
const target = ref(null);
const state = ref<'' | 'loading' | 'loaded' | 'complete' | 'error'>(''); // TODO: use ts-enum-util
const load = async (why?: string) => {
console.group('load');
console.debug(`why :>> `, why);
console.groupEnd();
if (state.value === 'loading') return;
state.value = 'loading';
try {
const { hasMore } = await props.asyncLoad();
state.value = hasMore ? 'loaded' : 'complete';
if (hasMore) {
await nextTick();
if (checkIsVisible(target.value!)) {
load('visible after load');
}
}
if (!hasMore) {
pause(); // TODO: 下拉刷新后,怎么恢复? maybe @register
}
} catch (error) {
state.value = 'error';
}
};
const { pause /* , resume, isSupported, isActive */ } = useIntersectionObserver(
target,
([entry]) => {
if (entry?.isIntersecting) {
load('visible in observer');
}
},
{
// 数值形式单个值表示目标元素可见部分与整个目标元素的比例。例如threshold: 0.5
// 目标元素可见比例达到 50% 时触发回调。
// 数组形式多个值表示多个可见比例触发点。例如threshold: [0, 0.25, 0.5, 0.75, 1.0]。
// 在目标元素可见部分从 0% 增加到 100% 时,每达到一个阈值都会触发回调。
threshold: 0,
},
);
</script>
<template>
<div class="infinite-loading" ref="target">
<div v-if="state === 'complete'" class="infinite-loading__complete">
<slot name="complete">
<span>没有更多了</span>
</slot>
</div>
<template v-else>
<div v-show="state == 'loading'" class="infinite-loading__loading">
<slot name="loading">
<span> 加载中... </span>
</slot>
</div>
<div
v-if="state === 'error'"
class="infinite-loading__error"
@click="
() => {
!$slots.error && load('click error');
}
"
>
<slot name="error" :retry="load">
<span> 加载失败点击重试 </span>
</slot>
</div>
</template>
</div>
</template>
<style scoped>
.infinite-loading__loading,
.infinite-loading__complete,
.infinite-loading__error {
display: flex;
justify-content: center;
align-items: center;
min-height: 40px;
color: #666;
}
.infinite-loading__error {
cursor: pointer;
}
</style>