From 2e61e9cb502f2bb6910f59abfb483fd2517e594f Mon Sep 17 00:00:00 2001 From: tanjinzhou <415800467@qq.com> Date: Wed, 4 Nov 2020 18:59:08 +0800 Subject: [PATCH] perf: update virtual-list --- components/vc-select/OptionList.tsx | 10 +++- components/vc-virtual-list/List.tsx | 53 ++++++++++++------- components/vc-virtual-list/ScrollBar.tsx | 23 ++++++-- .../vc-virtual-list/hooks/useScrollTo.tsx | 10 +++- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/components/vc-select/OptionList.tsx b/components/vc-select/OptionList.tsx index 840b9b2c60..40c437cca9 100644 --- a/components/vc-select/OptionList.tsx +++ b/components/vc-select/OptionList.tsx @@ -5,7 +5,7 @@ import classNames from '../_util/classNames'; import pickAttrs from '../_util/pickAttrs'; import { isValidElement } from '../_util/props-util'; import createRef from '../_util/createRef'; -import { computed, defineComponent, reactive, VNodeChild, watch } from 'vue'; +import { computed, defineComponent, nextTick, reactive, VNodeChild, watch } from 'vue'; import List from '../vc-virtual-list/List'; import { OptionsType as SelectOptionsType, @@ -153,8 +153,14 @@ const OptionList = defineComponent({ scrollIntoView(index); } }); + // Force trigger scrollbar visible when open + if (props.open) { + nextTick(()=>{ + listRef.current?.scrollTo(undefined); + }) + } }, - { immediate: true, flush: 'post' }, + { immediate: true }, ); // ========================== Values ========================== diff --git a/components/vc-virtual-list/List.tsx b/components/vc-virtual-list/List.tsx index f770cc56da..962ff7df32 100644 --- a/components/vc-virtual-list/List.tsx +++ b/components/vc-virtual-list/List.tsx @@ -84,16 +84,13 @@ const List = defineComponent({ }, setup(props) { // ================================= MISC ================================= - + const useVirtual = computed(()=>{ + const { height, itemHeight, virtual } = props; + return !!(virtual !== false && height && itemHeight); + }) const inVirtual = computed(() => { - const { height, itemHeight, data, virtual } = props; - return !!( - virtual !== false && - height && - itemHeight && - data && - itemHeight * data.length > height - ); + const { height, itemHeight, data } = props; + return useVirtual.value && data && itemHeight * data.length > height; }); const state = reactive({ @@ -103,7 +100,8 @@ const List = defineComponent({ }); const componentRef = ref(); - + const fillerInnerRef = ref(); + const scrollBarRef = ref(); // Hack on scrollbar to enable flash call // =============================== Item Key =============================== const getKey = (item: Record) => { if (typeof props.itemKey === 'function') { @@ -135,10 +133,9 @@ const List = defineComponent({ // ================================ Height ================================ const [setInstance, collectHeight, heights, heightUpdatedMark] = useHeights(getKey, null, null); - // ========================== Visible Calculation ========================= const calRes = computed(() => { - if (!inVirtual.value) { + if (!useVirtual.value) { return { scrollHeight: undefined, start: 0, @@ -146,6 +143,17 @@ const List = defineComponent({ offset: undefined, }; } + + // Always use virtual scroll bar in avoid shaking + if (!inVirtual.value) { + return { + scrollHeight: fillerInnerRef.value?.offsetHeight || 0, + start: 0, + end: state.mergedData.length - 1, + offset: undefined, + }; + } + let itemTop = 0; let startIndex: number | undefined; let startOffset: number | undefined; @@ -228,7 +236,7 @@ const List = defineComponent({ // Since this added in global,should use ref to keep update const [onRawWheel, onFireFoxScroll] = useFrameWheel( - inVirtual, + useVirtual, isScrollAtTop, isScrollAtBottom, offsetY => { @@ -240,7 +248,7 @@ const List = defineComponent({ ); // Mobile touch move - useMobileTouchMove(inVirtual, componentRef, (deltaY, smoothOffset) => { + useMobileTouchMove(useVirtual, componentRef, (deltaY, smoothOffset) => { if (originScroll(deltaY, smoothOffset)) { return false; } @@ -250,7 +258,7 @@ const List = defineComponent({ }); // Firefox only function onMozMousePixelScroll(e: MouseEvent) { - if (inVirtual.value) { + if (useVirtual.value) { e.preventDefault(); } } @@ -285,6 +293,9 @@ const List = defineComponent({ getKey, collectHeight, syncScrollTop, + () => { + scrollBarRef.value?.delayHidden(); + }, ); const componentStyle = computed(() => { @@ -292,7 +303,7 @@ const List = defineComponent({ if (props.height) { cs = { [props.fullHeight ? 'height' : 'maxHeight']: props.height + 'px', ...ScrollStyle }; - if (inVirtual.value) { + if (useVirtual.value) { cs!.overflowY = 'hidden'; if (state.scrollMoving) { @@ -310,11 +321,13 @@ const List = defineComponent({ onFallbackScroll, onScrollBar, componentRef, - inVirtual, + useVirtual, calRes, collectHeight, setInstance, sharedConfig, + scrollBarRef, + fillerInnerRef }; }, render() { @@ -341,7 +354,7 @@ const List = defineComponent({ componentStyle, onFallbackScroll, onScrollBar, - inVirtual, + useVirtual, collectHeight, sharedConfig, setInstance, @@ -375,13 +388,15 @@ const List = defineComponent({ height={scrollHeight} offset={offset} onInnerResize={collectHeight} + ref="fillerInnerRef" > {listChildren} - {inVirtual && ( + {useVirtual && ( { onScroll(newScrollTop); @@ -164,30 +164,45 @@ export default defineComponent({ getEnableScrollRange() { const { scrollHeight, height } = this.$props; - return scrollHeight - height; + return scrollHeight - height || 0; }, getEnableHeightRange() { const { height } = this.$props; const spinHeight = this.getSpinHeight(); - return height - spinHeight; + return height - spinHeight || 0; }, getTop() { const { scrollTop } = this.$props; const enableScrollRange = this.getEnableScrollRange(); const enableHeightRange = this.getEnableHeightRange(); + if (scrollTop === 0 || enableScrollRange === 0) { + return 0; + } const ptg = scrollTop / enableScrollRange; return ptg * enableHeightRange; }, + // Not show scrollbar when height is large thane scrollHeight + getVisible () { + const { visible } = this.state; + const { height, scrollHeight } = this.$props; + + if (height >= scrollHeight) { + return false; + } + + return visible; + }, }, render() { // eslint-disable-next-line no-unused-vars - const { visible, dragging } = this.state; + const { dragging } = this.state; const { prefixCls } = this.$props; const spinHeight = this.getSpinHeight() + 'px'; const top = this.getTop() + 'px'; + const visible = this.getVisible(); return (
void, syncScrollTop: (newTop: number) => void, + triggerFlash: () => void, ) { let scroll: number | null = null; - return (arg: any) => { + return (arg?: any) => { + // When not argument provided, we think dev may want to show the scrollbar + if (arg === null || arg === undefined) { + triggerFlash(); + return; + } + + // Normal scroll logic raf.cancel(scroll!); const data = state.mergedData; const itemHeight = props.itemHeight;