UNPKG

@coreui/vue-pro

Version:

UI Components Library for Vue.js

112 lines (103 loc) 3.63 kB
import { cloneVNode, computed, defineComponent, h, onMounted, ref, VNode } from 'vue' const CVirtualScroller = defineComponent({ name: 'CVirtualScroller', props: { /** * Amount of visible items */ visibleItems: { type: Number, default: 10, }, }, setup(props, { slots }) { const virtualScrollRef = ref<HTMLDivElement>() const virtualScrollContentRef = ref<HTMLDivElement>() const currentItemIndex = ref(1) const itemHeight = ref<number>(0) const itemsNumber = ref<number>(0) const viewportPadding = ref(0) const buffer = computed(() => Math.floor(props.visibleItems / 2)) const maxHeight = computed( () => itemsNumber.value * itemHeight.value + 2 * viewportPadding.value, ) const viewportHeight = computed( () => Math.min(props.visibleItems, itemsNumber.value) * itemHeight.value + 2 * viewportPadding.value, ) onMounted(() => { if (virtualScrollRef.value) { viewportPadding.value = Number.parseFloat( getComputedStyle(virtualScrollRef.value).paddingTop, ) // It's necessary to calculate heights of items virtualScrollRef.value.dispatchEvent(new CustomEvent('scroll')) } }) const handleScroll = (scrollTop: number) => { currentItemIndex.value = itemHeight.value && Math.max(Math.ceil(scrollTop / itemHeight.value), 1) } return () => { const children: any = slots.default ? Array.isArray(slots.default()[0].children) ? slots.default()[0].children : slots.default() : [] itemsNumber.value = children && children.length > 0 ? children.length : 0 return h( 'div', { class: ['virtual-scroller'], onScroll: (event: any) => handleScroll((event.target as HTMLDivElement).scrollTop), style: { height: `${viewportHeight.value}px`, overflowY: 'auto', }, ref: virtualScrollRef, }, h( 'div', { class: 'virtual-scroller-content', style: { height: `${maxHeight.value}px`, }, ref: virtualScrollContentRef, }, children.map( (slot: VNode, index: number) => index + 1 > Math.max(currentItemIndex.value - buffer.value, 0) && index + 1 <= currentItemIndex.value + props.visibleItems + buffer.value && cloneVNode(slot, { class: [ { 'virtual-scroller-item-preload': index + 1 > currentItemIndex.value + props.visibleItems || index + 1 < currentItemIndex.value, }, ], style: { ...(currentItemIndex.value > buffer.value && { transform: `translateY(${ (currentItemIndex.value - buffer.value) * itemHeight.value }px)`, }), }, ref: (node) => { if (itemHeight.value === 0 && node && (node as HTMLElement).offsetHeight) { itemHeight.value = (node as HTMLElement).offsetHeight + Number.parseFloat(getComputedStyle(node as HTMLElement).marginTop) + Number.parseFloat(getComputedStyle(node as HTMLElement).marginBottom) } }, }), ), ), ) } }, }) export { CVirtualScroller }