yunohost-admin/app/src/components/LazyRenderer.vue

88 lines
2.3 KiB
Vue

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
const props = withDefaults(
defineProps<{
unrender?: boolean
minHeight?: number
renderDelay?: number
unrenderDelay?: number
rootMargin?: string
}>(),
{
unrender: true,
minHeight: 0,
renderDelay: 100,
unrenderDelay: 2000,
rootMargin: '300px',
},
)
defineSlots<{
default: any
}>()
const observer = ref<IntersectionObserver | null>(null)
const render = ref(false)
const fixedMinHeight = ref(props.minHeight)
const rootElem = ref<HTMLDivElement | null>(null)
onMounted(() => {
let unrenderTimer: NodeJS.Timeout
let renderTimer: NodeJS.Timeout
observer.value = new IntersectionObserver(
(entries) => {
let intersecting = entries[0].isIntersecting
// Fix for weird bug when typing fast in app search or on slow client.
// Intersection is triggered but even if the element is indeed in the viewport,
// isIntersecting is `false`, so we have to manually check this…
// FIXME Would be great to find out why this is happening
if (!intersecting && rootElem.value!.offsetTop < window.innerHeight) {
intersecting = true
}
if (intersecting) {
clearTimeout(unrenderTimer)
// Show the component after a delay (to avoid rendering while scrolling fast)
renderTimer = setTimeout(
() => {
render.value = true
},
props.unrender ? props.renderDelay : 0,
)
if (!props.unrender) {
// Stop listening to intersections after first appearance if unrendering is not activated
this.observer.disconnect()
}
} else if (props.unrender) {
clearTimeout(renderTimer)
// Hide the component after a delay if it's no longer in the viewport
unrenderTimer = setTimeout(() => {
fixedMinHeight.value = rootElem.value!.clientHeight
render.value = false
}, props.unrenderDelay)
}
},
{ rootMargin: props.rootMargin },
)
observer.value.observe(rootElem.value!)
})
onBeforeUnmount(() => {
observer.value!.disconnect()
})
</script>
<template>
<div
ref="rootElem"
class="lazy-renderer"
:style="`min-height: ${fixedMinHeight}px`"
>
<slot v-if="render" />
</div>
</template>