index.tsx
3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { defineComponent, computed, ref, unref, reactive, onMounted, watch, nextTick } from 'vue';
import { useEvent } from '/@/hooks/event/useEvent';
import { convertToUnit } from '/@/components/util';
import { props as basicProps } from './props';
import { getSlot } from '/@/utils/helper/tsxHelper';
import './index.less';
const prefixCls = 'virtual-scroll';
export default defineComponent({
name: 'VirtualScroll',
props: basicProps,
setup(props, { slots }) {
const wrapElRef = ref<HTMLDivElement | null>(null);
const state = reactive({
first: 0,
last: 0,
scrollTop: 0,
});
const getBenchRef = computed(() => {
return parseInt(props.bench as string, 10);
});
const getItemHeightRef = computed(() => {
return parseInt(props.itemHeight as string, 10);
});
const getFirstToRenderRef = computed(() => {
return Math.max(0, state.first - unref(getBenchRef));
});
const getLastToRenderRef = computed(() => {
return Math.min((props.items || []).length, state.last + unref(getBenchRef));
});
const getContainerStyleRef = computed(() => {
return {
height: convertToUnit((props.items || []).length * unref(getItemHeightRef)),
};
});
const getWrapStyleRef = computed((): object => {
const styles: Record<string, string> = {};
const height = convertToUnit(props.height);
const minHeight = convertToUnit(props.minHeight);
const minWidth = convertToUnit(props.minWidth);
const maxHeight = convertToUnit(props.maxHeight);
const maxWidth = convertToUnit(props.maxWidth);
const width = convertToUnit(props.width);
if (height) styles.height = height;
if (minHeight) styles.minHeight = minHeight;
if (minWidth) styles.minWidth = minWidth;
if (maxHeight) styles.maxHeight = maxHeight;
if (maxWidth) styles.maxWidth = maxWidth;
if (width) styles.width = width;
return styles;
});
watch([() => props.itemHeight, () => props.height], () => {
onScroll();
});
function getLast(first: number): number {
const wrapEl = unref(wrapElRef);
if (!wrapEl) {
return 0;
}
const height = parseInt(props.height || 0, 10) || wrapEl.clientHeight;
return first + Math.ceil(height / unref(getItemHeightRef));
}
function getFirst(): number {
return Math.floor(state.scrollTop / unref(getItemHeightRef));
}
function onScroll() {
const wrapEl = unref(wrapElRef);
if (!wrapEl) {
return;
}
state.scrollTop = wrapEl.scrollTop;
state.first = getFirst();
state.last = getLast(state.first);
}
function renderChildren() {
const { items = [] } = props;
return items.slice(unref(getFirstToRenderRef), unref(getLastToRenderRef)).map(genChild);
}
function genChild(item: any, index: number) {
index += unref(getFirstToRenderRef);
const top = convertToUnit(index * unref(getItemHeightRef));
return (
<div class={`${prefixCls}__item`} style={{ top }} key={index}>
{getSlot(slots, 'default', { index, item })}
</div>
);
}
onMounted(() => {
state.last = getLast(0);
nextTick(() => {
const wrapEl = unref(wrapElRef);
if (!wrapEl) {
return;
}
useEvent({
el: wrapEl,
name: 'scroll',
listener: onScroll,
wait: 0,
});
});
});
return () => (
<div class={prefixCls} style={unref(getWrapStyleRef)} ref={wrapElRef}>
<div class={`${prefixCls}__container`} style={unref(getContainerStyleRef)}>
{renderChildren()}
</div>
</div>
);
},
});