koel/resources/assets/js/components/ui/VirtualScroller.vue

76 lines
2.2 KiB
Vue
Raw Normal View History

2022-04-27 20:52:37 +00:00
<template>
2024-04-04 22:20:42 +00:00
<div
ref="scroller"
v-koel-overflow-fade
class="virtual-scroller will-change-transform overflow-scroll"
@scroll.passive="onScroll"
>
2024-04-23 21:01:27 +00:00
<div :style="{ height: `${totalHeight}px` }" class="will-change-transform overflow-hidden">
<div :style="{ transform: `translateY(${offsetY}px)`}" class="will-change-transform">
<slot v-for="item in renderedItems" :item="item" />
2022-04-27 20:52:37 +00:00
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted, ref, toRefs } from 'vue'
2022-04-27 20:52:37 +00:00
const props = defineProps<{ items: any[], itemHeight: number }>()
const { items, itemHeight } = toRefs(props)
const scroller = ref<HTMLElement>()
const scrollerHeight = ref(0)
const renderAhead = 5
const scrollTop = ref(0)
const emit = defineEmits<{
(e: 'scrolled-to-end'): void,
(e: 'scroll', event: Event): void
}>()
2022-06-10 10:47:46 +00:00
2022-04-27 20:52:37 +00:00
const totalHeight = computed(() => items.value.length * itemHeight.value)
const startPosition = computed(() => Math.max(0, Math.floor(scrollTop.value / itemHeight.value) - renderAhead))
2022-04-27 20:52:37 +00:00
const offsetY = computed(() => startPosition.value * itemHeight.value)
const renderedItems = computed(() => {
let count = Math.ceil(scrollerHeight.value / itemHeight.value) + 2 * renderAhead
count = Math.min(items.value.length - startPosition.value, count)
return items.value.slice(startPosition.value, startPosition.value + count)
})
const onScroll = (e: Event) => requestAnimationFrame(() => {
2022-06-10 10:47:46 +00:00
scrollTop.value = (e.target as HTMLElement).scrollTop
if (!scroller.value) return
2022-07-16 09:52:39 +00:00
emit('scroll', e)
2022-06-10 10:47:46 +00:00
if (scroller.value.scrollTop + scroller.value.clientHeight + itemHeight.value >= scroller.value.scrollHeight) {
emit('scrolled-to-end')
}
})
2022-04-27 20:52:37 +00:00
const observer = new ResizeObserver(entries => entries.forEach(el => scrollerHeight.value = el.contentRect.height))
onMounted(() => {
observer.observe(scroller.value!)
scrollerHeight.value = scroller.value!.offsetHeight
})
2022-04-30 10:36:09 +00:00
onBeforeUnmount(() => observer.unobserve(scroller.value!))
2022-04-27 20:52:37 +00:00
</script>
2024-04-04 20:13:35 +00:00
<style lang="postcss" scoped>
2022-04-27 20:52:37 +00:00
.virtual-scroller {
@supports (scrollbar-gutter: stable) {
overflow: auto;
scrollbar-gutter: stable;
2022-07-29 12:12:55 +00:00
@media (hover: none) {
scrollbar-gutter: auto;
}
}
2022-04-27 20:52:37 +00:00
}
</style>