ytptube/ui/app/components/LateLoader.vue
2026-02-20 22:46:30 +03:00

84 lines
1.8 KiB
Vue

<template>
<div ref="targetEl" :style="`min-height:${fixedMinHeight || props.minHeight || 0}px`">
<slot v-if="shouldRender" />
</div>
</template>
<script setup lang="ts">
import { useIntersectionObserver } from '@vueuse/core';
const props = defineProps<{
renderOnIdle?: boolean;
unrender?: boolean;
minHeight?: number;
unrenderDelay?: number;
}>();
const shouldRender = ref(false);
const targetEl = ref<HTMLElement | null>(null);
const fixedMinHeight = ref(0);
let unrenderTimer: ReturnType<typeof setTimeout> | null = null;
let renderTimer: ReturnType<typeof setTimeout> | null = null;
function onIdle(cb: () => void): void {
if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(cb);
} else {
setTimeout(() => nextTick(cb), 300);
}
}
const { stop } = useIntersectionObserver(
targetEl,
(entries) => {
const entry = entries[0];
if (entry?.isIntersecting) {
if (unrenderTimer) clearTimeout(unrenderTimer);
renderTimer = setTimeout(
() => {
shouldRender.value = true;
},
props.unrender ? 200 : 0,
);
shouldRender.value = true;
if (!props.unrender) {
stop();
}
} else if (props.unrender) {
if (renderTimer) {
clearTimeout(renderTimer);
}
unrenderTimer = setTimeout(() => {
if (targetEl.value?.clientHeight) {
fixedMinHeight.value = targetEl.value.clientHeight;
}
shouldRender.value = false;
}, props.unrenderDelay ?? 6000);
}
},
{ rootMargin: '600px' },
);
if (props.renderOnIdle) {
onIdle(() => {
shouldRender.value = true;
if (!props.unrender) {
stop();
}
});
}
onBeforeUnmount(() => {
if (renderTimer) {
clearTimeout(renderTimer);
}
if (unrenderTimer) {
clearTimeout(unrenderTimer);
}
});
</script>