2020-06-11 23:34:38 +02:00
|
|
|
<template>
|
2022-05-10 13:02:01 +02:00
|
|
|
<div ref="root" class="vscroll-holder">
|
2020-06-11 23:34:38 +02:00
|
|
|
<div
|
|
|
|
class="vscroll-spacer"
|
|
|
|
:style="{
|
|
|
|
opacity: 0,
|
|
|
|
clear: 'both',
|
|
|
|
height: topHeight + 'px'
|
|
|
|
}"
|
|
|
|
/>
|
|
|
|
<slot :items="visibleItems" />
|
|
|
|
<div
|
|
|
|
class="vscroll-spacer"
|
|
|
|
:style="{
|
|
|
|
opacity: 0,
|
|
|
|
clear: 'both',
|
|
|
|
height: bottomHeight + 'px'
|
|
|
|
}"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2022-05-10 13:02:01 +02:00
|
|
|
<script setup lang="ts">
|
|
|
|
import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
items: Array,
|
|
|
|
itemHeight: Number,
|
|
|
|
visibleHeight: Number,
|
|
|
|
scrollElement: {
|
|
|
|
type: HTMLDivElement,
|
|
|
|
default: null
|
2020-06-11 23:34:38 +02:00
|
|
|
}
|
2022-05-10 13:02:01 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const root = ref(null);
|
|
|
|
const topHeight: Ref<number> = ref(0);
|
|
|
|
const bottomHeight: Ref<number> = ref(0);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
const visibleItems: Ref<any[]> = ref([]);
|
|
|
|
const renderTimeout: Ref<NodeJS.Timeout> = ref(null);
|
|
|
|
const localScrollElement: Ref<HTMLDivElement> = ref(null);
|
|
|
|
|
|
|
|
const checkScrollPosition = () => {
|
|
|
|
clearTimeout(renderTimeout.value);
|
|
|
|
|
|
|
|
renderTimeout.value = setTimeout(() => {
|
|
|
|
updateWindow();
|
|
|
|
}, 200);
|
|
|
|
};
|
|
|
|
|
|
|
|
const updateWindow = () => {
|
|
|
|
const visibleItemsCount = Math.ceil(props.visibleHeight / props.itemHeight);
|
|
|
|
const totalScrollHeight = props.items.length * props.itemHeight;
|
|
|
|
const offset = 50;
|
|
|
|
|
|
|
|
const scrollTop = localScrollElement.value.scrollTop;
|
|
|
|
|
|
|
|
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
|
|
|
|
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
|
|
|
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
|
|
|
const lastCutIndex = lastVisibleIndex + offset;
|
|
|
|
|
|
|
|
visibleItems.value = props.items.slice(firstCutIndex, lastCutIndex);
|
|
|
|
|
|
|
|
topHeight.value = firstCutIndex * props.itemHeight;
|
|
|
|
bottomHeight.value = totalScrollHeight - visibleItems.value.length * props.itemHeight - topHeight.value;
|
2020-06-11 23:34:38 +02:00
|
|
|
};
|
2022-05-10 13:02:01 +02:00
|
|
|
|
|
|
|
const setScrollElement = () => {
|
|
|
|
if (localScrollElement.value)
|
|
|
|
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
|
|
|
|
|
2022-05-11 11:27:29 +02:00
|
|
|
localScrollElement.value = props.scrollElement ? props.scrollElement : root.value;
|
2022-05-10 13:02:01 +02:00
|
|
|
updateWindow();
|
|
|
|
localScrollElement.value.addEventListener('scroll', checkScrollPosition);
|
|
|
|
};
|
|
|
|
|
2022-05-11 11:27:29 +02:00
|
|
|
watch(() => props.scrollElement, () => {
|
2022-05-10 13:02:01 +02:00
|
|
|
setScrollElement();
|
|
|
|
});
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
setScrollElement();
|
|
|
|
});
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
|
|
|
|
});
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
updateWindow
|
|
|
|
});
|
|
|
|
|
2020-06-11 23:34:38 +02:00
|
|
|
</script>
|