UNPKG

vue-tree-kl

Version:

A vue tree component using virtual list.

172 lines (160 loc) 5.41 kB
import { ref } from "vue" import { TreeNode } from ".." import { INonReactiveData, TreeNodeKeyType } from "../types" import { VerticalPositionType, verticalPositionEnum } from "../constants" import { TreeProps } from "../components/Tree.vue" type IUseVirtualListProps = Required<Pick<TreeProps, 'renderNodeAmount' | 'nodeMinHeight' | 'bufferNodeAmount' | 'keyField' >> export const useVirtualList = (nonReactive: INonReactiveData, props: IUseVirtualListProps) => { const scrollArea = ref() const renderNodes = ref<TreeNode[]>([]) const blockLength = ref(0) const blockAreaHeight = ref(0) const topSpaceHeight = ref(0) const bottomSpaceHeight = ref(0) const renderAmount = ref(0) const renderAmountCache = ref(0) const renderStart = ref(0) const renderStartCache = ref(0) const debounceTimer = ref<number | undefined>(undefined) /** * 重置空白与滚动高度 */ const resetSpaceHeights = (): void => { topSpaceHeight.value = 0 bottomSpaceHeight.value = 0 if (scrollArea.value) scrollArea.value.scrollTop = 0 } /** * 计算需要渲染的节点的数量,只要容器高度(clientHeight)不变,这个数量一般就不会变 */ const updateRenderAmount = (): void => { const clientHeight = scrollArea.value.clientHeight renderAmount.value = Math.max( props.renderNodeAmount, Math.ceil(clientHeight / props.nodeMinHeight) + props.bufferNodeAmount ) } /** * 计算渲染的节点,基于 scrollTop 计算当前应该渲染哪些节点 */ const updateRenderNodes = (isScroll: boolean = false): void => { if (blockLength.value > renderAmount.value) { const scrollTop = Math.max(scrollArea.value.scrollTop, 0) /** 当前滚动了多少节点 */ const scrollNodeAmount = Math.floor(scrollTop / props.nodeMinHeight) renderStart.value = Math.floor(scrollNodeAmount / props.bufferNodeAmount) * props.bufferNodeAmount } else { renderStart.value = 0 } if ( isScroll && renderAmountCache.value === renderAmount.value && renderStartCache.value === renderStart.value ) return renderNodes.value = nonReactive.blockNodes .slice(renderStart.value, renderStart.value + renderAmount.value) .map(blockNode => { return Object.assign({}, blockNode, { _parent: null, children: [] }) }) as TreeNode[] topSpaceHeight.value = renderStart.value * props.nodeMinHeight bottomSpaceHeight.value = blockAreaHeight.value - (topSpaceHeight.value + renderNodes.value.length * props.nodeMinHeight) } /** * 计算渲染节点数量,并计算渲染节点 */ const updateRender = (): void => { updateRenderAmount() updateRenderNodes() } /** * 计算可见节点 */ const updateBlockNodes = (): void => { nonReactive.blockNodes = nonReactive.store.flatData.filter( node => node.visible ) updateBlockData() updateRender() } /** * 更新 block 数据相关信息 */ const updateBlockData = (): void => { blockLength.value = nonReactive.blockNodes.length blockAreaHeight.value = props.nodeMinHeight * blockLength.value } const handleTreeScroll = (): void => { if (debounceTimer.value) { window.cancelAnimationFrame(debounceTimer.value) } renderAmountCache.value = renderAmount.value renderStartCache.value = renderStart.value debounceTimer.value = window.requestAnimationFrame( updateRenderNodes.bind(null, true) ) } /** * 滚动到指定节点位置 * @param key 要滚动的节点 * @param verticalPosition 滚动的垂直位置,可选为 'top', 'center', 'bottom' 或距离容器可视顶部距离的数字,默认为 'top' */ const scrollTo = ( key: TreeNodeKeyType, verticalPosition: VerticalPositionType | number = verticalPositionEnum.top ): void => { const node = nonReactive.store.mapData[key] if (!node || !node.visible) return let index: number = -1 for (let i = 0; i < blockLength.value; i++) { if (nonReactive.blockNodes[i][props.keyField] === key) { index = i break } } if (index === -1) return let scrollTop = index * props.nodeMinHeight if (verticalPosition === verticalPositionEnum.center) { const clientHeight = scrollArea.value.clientHeight scrollTop = scrollTop - (clientHeight - props.nodeMinHeight) / 2 } else if (verticalPosition === verticalPositionEnum.bottom) { const clientHeight = scrollArea.value.clientHeight scrollTop = scrollTop - (clientHeight - props.nodeMinHeight) } else if (typeof verticalPosition === 'number') { scrollTop = scrollTop - verticalPosition } if (scrollArea.value) scrollArea.value.scrollTop = scrollTop } return { scrollArea, renderNodes, blockLength, blockAreaHeight, topSpaceHeight, bottomSpaceHeight, renderAmount, renderAmountCache, renderStart, renderStartCache, resetSpaceHeights, updateRenderAmount, updateRenderNodes, updateRender, updateBlockNodes, updateBlockData, handleTreeScroll, scrollTo, } }