LazyContainer.vue
2.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<script lang="ts" setup>
import { onMounted, reactive, ref, toRef } from 'vue'
import { Skeleton } from 'ant-design-vue'
import { useTimeoutFn } from '@vueuse/core'
import { basicProps } from './LazyContainer.props'
import { useIntersectionObserver } from '@/hooks/event/useIntersectionObserver'
interface State {
isInit: boolean
loading: boolean
intersectionObserverInstance: IntersectionObserver | null
}
const props = defineProps(basicProps)
const emit = defineEmits(['init'])
const elRef = ref()
const state = reactive<State>({
isInit: false,
loading: false,
intersectionObserverInstance: null,
})
onMounted(() => {
immediateInit()
initIntersectionObserver()
})
// If there is a set delay time, it will be executed immediately
function immediateInit() {
const { timeout } = props
timeout
&& useTimeoutFn(() => {
init()
}, timeout)
}
function init() {
state.loading = true
useTimeoutFn(() => {
if (state.isInit) return
state.isInit = true
emit('init')
}, props.maxWaitingTime || 80)
}
function initIntersectionObserver() {
const { timeout, direction, threshold } = props
if (timeout) return
// According to the scrolling direction to construct the viewport margin, used to load in advance
let rootMargin = '0px'
switch (direction) {
case 'vertical':
rootMargin = `${threshold} 0px`
break
case 'horizontal':
rootMargin = `0px ${threshold}`
break
}
try {
const { stop, observer } = useIntersectionObserver({
rootMargin,
target: toRef(elRef.value, '$el'),
onIntersect: (entries: any[]) => {
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
if (isIntersecting) {
init()
if (observer)
stop()
}
},
root: toRef(props, 'viewport'),
})
}
catch (e) {
init()
}
}
</script>
<template>
<transition-group
v-bind="$attrs"
ref="elRef"
class="h-full w-full box-border"
:name="transitionName"
:tag="tag"
mode="out-in"
>
<div v-if="state.isInit" key="component">
<slot :loading="state.loading" />
</div>
<div v-else key="skeleton">
<slot v-if="$slots.skeleton" name="skeleton" />
<Skeleton v-else />
</div>
</transition-group>
</template>