|
|
@ -12,12 +12,21 @@ |
|
|
<script lang="ts"> |
|
|
<script lang="ts"> |
|
|
import type { PropType } from 'vue'; |
|
|
import type { PropType } from 'vue'; |
|
|
|
|
|
|
|
|
import { defineComponent, reactive, onMounted, ref, unref, onUnmounted, toRefs } from 'vue'; |
|
|
import { |
|
|
|
|
|
defineComponent, |
|
|
|
|
|
reactive, |
|
|
|
|
|
onMounted, |
|
|
|
|
|
ref, |
|
|
|
|
|
unref, |
|
|
|
|
|
onUnmounted, |
|
|
|
|
|
toRef, |
|
|
|
|
|
toRefs, |
|
|
|
|
|
} from 'vue'; |
|
|
|
|
|
|
|
|
import { Skeleton } from 'ant-design-vue'; |
|
|
import { Skeleton } from 'ant-design-vue'; |
|
|
import { useRaf } from '/@/hooks/event/useRaf'; |
|
|
import { useRaf } from '/@/hooks/event/useRaf'; |
|
|
import { useTimeout } from '/@/hooks/core/useTimeout'; |
|
|
import { useTimeout } from '/@/hooks/core/useTimeout'; |
|
|
|
|
|
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; |
|
|
interface State { |
|
|
interface State { |
|
|
isInit: boolean; |
|
|
isInit: boolean; |
|
|
loading: boolean; |
|
|
loading: boolean; |
|
|
@ -30,7 +39,7 @@ |
|
|
// 等待时间,如果指定了时间,不论可见与否,在指定时间之后自动加载 |
|
|
// 等待时间,如果指定了时间,不论可见与否,在指定时间之后自动加载 |
|
|
timeout: { |
|
|
timeout: { |
|
|
type: Number as PropType<number>, |
|
|
type: Number as PropType<number>, |
|
|
default: 8000, |
|
|
default: 0, |
|
|
// default: 8000, |
|
|
// default: 8000, |
|
|
}, |
|
|
}, |
|
|
// 组件所在的视口,如果组件是在页面容器内滚动,视口就是该容器 |
|
|
// 组件所在的视口,如果组件是在页面容器内滚动,视口就是该容器 |
|
|
@ -40,6 +49,7 @@ |
|
|
>, |
|
|
>, |
|
|
default: () => null, |
|
|
default: () => null, |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 预加载阈值, css单位 |
|
|
// 预加载阈值, css单位 |
|
|
threshold: { |
|
|
threshold: { |
|
|
type: String as PropType<string>, |
|
|
type: String as PropType<string>, |
|
|
@ -51,6 +61,7 @@ |
|
|
type: String as PropType<'vertical' | 'horizontal'>, |
|
|
type: String as PropType<'vertical' | 'horizontal'>, |
|
|
default: 'vertical', |
|
|
default: 'vertical', |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 包裹组件的外层容器的标签名 |
|
|
// 包裹组件的外层容器的标签名 |
|
|
tag: { |
|
|
tag: { |
|
|
type: String as PropType<string>, |
|
|
type: String as PropType<string>, |
|
|
@ -62,20 +73,14 @@ |
|
|
default: 80, |
|
|
default: 80, |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// // 是否在不可见的时候销毁 |
|
|
|
|
|
// autoDestory: { |
|
|
|
|
|
// type: Boolean as PropType<boolean>, |
|
|
|
|
|
// default: false, |
|
|
|
|
|
// }, |
|
|
|
|
|
|
|
|
|
|
|
// transition name |
|
|
// transition name |
|
|
transitionName: { |
|
|
transitionName: { |
|
|
type: String as PropType<string>, |
|
|
type: String as PropType<string>, |
|
|
default: 'lazy-container', |
|
|
default: 'lazy-container', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
emits: ['before-init', 'init'], |
|
|
emits: ['init'], |
|
|
setup(props, { emit, slots }) { |
|
|
setup(props, { emit }) { |
|
|
const elRef = ref<any>(null); |
|
|
const elRef = ref<any>(null); |
|
|
const state = reactive<State>({ |
|
|
const state = reactive<State>({ |
|
|
isInit: false, |
|
|
isInit: false, |
|
|
@ -83,17 +88,10 @@ |
|
|
intersectionObserverInstance: null, |
|
|
intersectionObserverInstance: null, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
immediateInit(); |
|
|
|
|
|
onMounted(() => { |
|
|
onMounted(() => { |
|
|
|
|
|
immediateInit(); |
|
|
initIntersectionObserver(); |
|
|
initIntersectionObserver(); |
|
|
}); |
|
|
}); |
|
|
onUnmounted(() => { |
|
|
|
|
|
// Cancel the observation before the component is destroyed |
|
|
|
|
|
if (state.intersectionObserverInstance) { |
|
|
|
|
|
const el = unref(elRef); |
|
|
|
|
|
state.intersectionObserverInstance.unobserve(el.$el); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// If there is a set delay time, it will be executed immediately |
|
|
// If there is a set delay time, it will be executed immediately |
|
|
function immediateInit() { |
|
|
function immediateInit() { |
|
|
@ -105,9 +103,6 @@ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function init() { |
|
|
function init() { |
|
|
// At this point, the skeleton component is about to be switched |
|
|
|
|
|
emit('before-init'); |
|
|
|
|
|
// At this point you can prepare to load the resources of the lazy-loaded component |
|
|
|
|
|
state.loading = true; |
|
|
state.loading = true; |
|
|
|
|
|
|
|
|
requestAnimationFrameFn(() => { |
|
|
requestAnimationFrameFn(() => { |
|
|
@ -120,9 +115,7 @@ |
|
|
// Prevent waiting too long without executing the callback |
|
|
// Prevent waiting too long without executing the callback |
|
|
// Set the maximum waiting time |
|
|
// Set the maximum waiting time |
|
|
useTimeout(() => { |
|
|
useTimeout(() => { |
|
|
if (state.isInit) { |
|
|
if (state.isInit) return; |
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
callback(); |
|
|
callback(); |
|
|
}, props.maxWaitingTime || 80); |
|
|
}, props.maxWaitingTime || 80); |
|
|
|
|
|
|
|
|
@ -132,12 +125,10 @@ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initIntersectionObserver() { |
|
|
function initIntersectionObserver() { |
|
|
const { timeout, direction, threshold, viewport } = props; |
|
|
const { timeout, direction, threshold } = props; |
|
|
if (timeout) { |
|
|
if (timeout) return; |
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
// According to the scrolling direction to construct the viewport margin, used to load in advance |
|
|
// According to the scrolling direction to construct the viewport margin, used to load in advance |
|
|
let rootMargin; |
|
|
let rootMargin: string = '0px'; |
|
|
switch (direction) { |
|
|
switch (direction) { |
|
|
case 'vertical': |
|
|
case 'vertical': |
|
|
rootMargin = `${threshold} 0px`; |
|
|
rootMargin = `${threshold} 0px`; |
|
|
@ -146,35 +137,26 @@ |
|
|
rootMargin = `0px ${threshold}`; |
|
|
rootMargin = `0px ${threshold}`; |
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// Observe the intersection of the viewport and the component container |
|
|
const { stop, observer } = useIntersectionObserver({ |
|
|
state.intersectionObserverInstance = new window.IntersectionObserver( |
|
|
rootMargin, |
|
|
intersectionHandler, |
|
|
target: toRef(elRef.value, '$el'), |
|
|
{ |
|
|
onIntersect: (entries: any[]) => { |
|
|
rootMargin, |
|
|
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio; |
|
|
root: viewport, |
|
|
if (isIntersecting) { |
|
|
threshold: [0, Number.MIN_VALUE, 0.01], |
|
|
init(); |
|
|
} |
|
|
if (observer) { |
|
|
); |
|
|
stop(); |
|
|
|
|
|
} |
|
|
const el = unref(elRef); |
|
|
} |
|
|
|
|
|
}, |
|
|
state.intersectionObserverInstance.observe(el.$el); |
|
|
root: toRef(props, 'viewport'), |
|
|
|
|
|
}); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
init(); |
|
|
init(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
// Cross-condition change handling function |
|
|
|
|
|
function intersectionHandler(entries: any[]) { |
|
|
|
|
|
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio; |
|
|
|
|
|
if (isIntersecting) { |
|
|
|
|
|
init(); |
|
|
|
|
|
if (state.intersectionObserverInstance) { |
|
|
|
|
|
const el = unref(elRef); |
|
|
|
|
|
state.intersectionObserverInstance.unobserve(el.$el); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return { |
|
|
return { |
|
|
elRef, |
|
|
elRef, |
|
|
...toRefs(state), |
|
|
...toRefs(state), |
|
|
|