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>