@coreui/vue-pro
Version:
UI Components Library for Vue.js
112 lines (103 loc) • 3.63 kB
text/typescript
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 }