Skip to content

Commit 276db0d

Browse files
committed
refactor: event
1 parent f5a3f79 commit 276db0d

File tree

5 files changed

+96
-79
lines changed

5 files changed

+96
-79
lines changed

package-lock.json

+1-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue3-virtual-scroll-list",
3-
"version": "0.1.2",
3+
"version": "0.2.0",
44
"description": "A vue3 component support big amount data list with high scroll performance.",
55
"main": "dist/index.js",
66
"scripts": {
@@ -50,7 +50,6 @@
5050
"lint-staged": "^11.1.2",
5151
"prettier": "^2.3.2",
5252
"rollup": "^2.56.2",
53-
"tiny-emitter": "^2.1.0",
5453
"typescript": "^4.3.5",
5554
"vue": "^3.2.2"
5655
}

rollup.config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import babel from '@rollup/plugin-babel';
22
import resolve from '@rollup/plugin-node-resolve';
3-
import commonjs from '@rollup/plugin-commonjs';
3+
// import commonjs from '@rollup/plugin-commonjs';
44
import pkg from './package.json';
55

66
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
@@ -24,7 +24,7 @@ export default {
2424
resolve({
2525
extensions,
2626
}),
27-
commonjs(),
27+
// commonjs(),
2828
babel({ extensions, babelHelpers: 'bundled' }),
2929
],
3030
};

src/item.tsx

+12-8
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import {
77
ref,
88
Ref,
99
} from 'vue';
10-
// TODO: remove this
11-
import emitter from 'tiny-emitter/instance';
1210
import { ItemProps, SlotProps } from './props';
1311

14-
const useResizeChange = (props: any, rootRef: Ref<HTMLElement | null>) => {
12+
const useResizeChange = (
13+
props: any,
14+
rootRef: Ref<HTMLElement | null>,
15+
emit: any,
16+
) => {
1517
let resizeObserver: ResizeObserver | null = null;
1618
const shapeKey = computed(() =>
1719
props.horizontal ? 'offsetWidth' : 'offsetHeight',
@@ -24,7 +26,7 @@ const useResizeChange = (props: any, rootRef: Ref<HTMLElement | null>) => {
2426
// tell parent current size identify by unqiue key
2527
const dispatchSizeChange = () => {
2628
const { event, uniqueKey, hasInitial } = props;
27-
emitter.emit(event, uniqueKey, getCurrentSize(), hasInitial);
29+
emit(event, uniqueKey, getCurrentSize(), hasInitial);
2830
};
2931

3032
onMounted(() => {
@@ -51,9 +53,10 @@ const useResizeChange = (props: any, rootRef: Ref<HTMLElement | null>) => {
5153
export const Item = defineComponent({
5254
name: 'VirtualListItem',
5355
props: ItemProps,
54-
setup(props) {
56+
emits: ['itemResize'],
57+
setup(props, { emit }) {
5558
const rootRef = ref<HTMLElement | null>(null);
56-
useResizeChange(props, rootRef);
59+
useResizeChange(props, rootRef, emit);
5760

5861
return () => {
5962
const {
@@ -83,9 +86,10 @@ export const Item = defineComponent({
8386
export const Slot = defineComponent({
8487
name: 'VirtualListSlot',
8588
props: SlotProps,
86-
setup(props, { slots }) {
89+
emits: ['slotResize'],
90+
setup(props, { slots, emit }) {
8791
const rootRef = ref<HTMLElement | null>(null);
88-
useResizeChange(props, rootRef);
92+
useResizeChange(props, rootRef, emit);
8993

9094
return () => {
9195
const { tag: Tag, uniqueKey } = props;

src/virtual-list.tsx

+80-60
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
11
import {
2-
computed,
32
defineComponent,
4-
h,
53
onActivated,
64
onBeforeMount,
75
onMounted,
6+
onUnmounted,
87
ref,
98
watch,
109
} from 'vue';
1110
import Virtual from './virtual';
1211
import { Item, Slot } from './item';
1312
import { VirtualProps } from './props';
14-
// TODO: remove this
15-
import emitter from 'tiny-emitter/instance';
1613

1714
enum EVENT_TYPE {
18-
ITEM = 'item_resize',
19-
SLOT = 'slot_resize',
15+
ITEM = 'itemResize',
16+
SLOT = 'slotResize',
2017
}
2118

2219
enum SLOT_TYPE {
2320
HEADER = 'thead', // string value also use for aria role attribute
2421
FOOTER = 'tfoot',
2522
}
2623

24+
interface Range {
25+
start: number;
26+
end: number;
27+
padFront: number;
28+
padBehind: number;
29+
}
30+
2731
export default defineComponent({
2832
name: 'VirtualList',
2933
props: VirtualProps,
3034
setup(props, { emit, slots, expose }) {
31-
// TODO: TS
32-
const range = ref<any>(null);
35+
const isHorizontal = props.direction === 'horizontal';
36+
const directionKey = isHorizontal ? 'scrollLeft' : 'scrollTop';
37+
const range = ref<Range | null>(null);
3338
const root = ref<HTMLElement | null>();
3439
const shepherd = ref<HTMLDivElement | null>(null);
35-
const isHorizontal = computed(() => props.direction === 'horizontal');
36-
const directionKey = computed(() =>
37-
isHorizontal.value ? 'scrollLeft' : 'scrollTop',
38-
);
3940
let virtual: Virtual;
4041

4142
/**
@@ -69,19 +70,22 @@ export default defineComponent({
6970
/**
7071
* methods
7172
*/
73+
// get item size by id
74+
const getSize = (id) => {
75+
return virtual.sizes.get(id);
76+
};
7277
const getOffset = () => {
7378
if (props.pageMode) {
7479
return (
75-
document.documentElement[directionKey.value] ||
76-
document.body[directionKey.value]
80+
document.documentElement[directionKey] || document.body[directionKey]
7781
);
7882
} else {
79-
return root.value ? Math.ceil(root.value[directionKey.value]) : 0;
83+
return root.value ? Math.ceil(root.value[directionKey]) : 0;
8084
}
8185
};
8286
// return client viewport size
8387
const getClientSize = () => {
84-
const key = isHorizontal.value ? 'clientWidth' : 'clientHeight';
88+
const key = isHorizontal ? 'clientWidth' : 'clientHeight';
8589
if (props.pageMode) {
8690
return document.documentElement[key] || document.body[key];
8791
} else {
@@ -90,7 +94,7 @@ export default defineComponent({
9094
};
9195
// return all scroll size
9296
const getScrollSize = () => {
93-
const key = isHorizontal.value ? 'scrollWidth' : 'scrollHeight';
97+
const key = isHorizontal ? 'scrollWidth' : 'scrollHeight';
9498
if (props.pageMode) {
9599
return document.documentElement[key] || document.body[key];
96100
} else {
@@ -167,11 +171,11 @@ export default defineComponent({
167171
// set current scroll position to a expectant offset
168172
const scrollToOffset = (offset: number) => {
169173
if (props.pageMode) {
170-
document.body[directionKey.value] = offset;
171-
document.documentElement[directionKey.value] = offset;
174+
document.body[directionKey] = offset;
175+
document.documentElement[directionKey] = offset;
172176
} else {
173177
if (root.value) {
174-
root.value[directionKey.value] = offset;
178+
root.value[directionKey] = offset;
175179
}
176180
}
177181
};
@@ -204,7 +208,7 @@ export default defineComponent({
204208
index={index}
205209
tag={itemTag}
206210
event={EVENT_TYPE.ITEM}
207-
horizontal={isHorizontal.value}
211+
horizontal={isHorizontal}
208212
uniqueKey={uniqueKey}
209213
source={dataSource}
210214
extraProps={extraProps}
@@ -214,6 +218,7 @@ export default defineComponent({
214218
class={`${itemClass}${
215219
props.itemClassAdd ? ' ' + props.itemClassAdd(index) : ''
216220
}`}
221+
onItemResize={onItemResized}
217222
/>,
218223
);
219224
} else {
@@ -247,19 +252,47 @@ export default defineComponent({
247252
}
248253
};
249254

255+
// set current scroll position to bottom
256+
const scrollToBottom = () => {
257+
if (shepherd.value) {
258+
const offset =
259+
shepherd.value[isHorizontal ? 'offsetLeft' : 'offsetTop'];
260+
scrollToOffset(offset);
261+
262+
// check if it's really scrolled to the bottom
263+
// maybe list doesn't render and calculate to last range
264+
// so we need retry in next event loop until it really at bottom
265+
setTimeout(() => {
266+
if (getOffset() + getClientSize() < getScrollSize()) {
267+
scrollToBottom();
268+
}
269+
}, 3);
270+
}
271+
};
272+
273+
// when using page mode we need update slot header size manually
274+
// taking root offset relative to the browser as slot header size
275+
const updatePageModeFront = () => {
276+
if (root.value) {
277+
const rect = root.value.getBoundingClientRect();
278+
const { defaultView } = root.value.ownerDocument;
279+
const offsetFront = isHorizontal
280+
? rect.left + defaultView!.pageXOffset
281+
: rect.top + defaultView!.pageYOffset;
282+
virtual.updateParam('slotHeaderSize', offsetFront);
283+
}
284+
};
285+
286+
// get the total number of stored (rendered) items
287+
const getSizes = () => {
288+
return virtual.sizes.size;
289+
};
290+
250291
/**
251292
* life cycles
252293
*/
253294
onBeforeMount(() => {
254295
installVirtual();
255-
256-
// listen item size change
257-
emitter.on(EVENT_TYPE.ITEM, onItemResized);
258-
259-
// listen slot size change
260-
if (slots.header || slots.footer) {
261-
emitter.on(EVENT_TYPE.SLOT, onSlotResized);
262-
}
263296
});
264297

265298
// set back offset when awake from keep-alive
@@ -277,38 +310,23 @@ export default defineComponent({
277310

278311
// in page mode we bind scroll event to document
279312
if (props.pageMode) {
280-
// todo
313+
updatePageModeFront();
314+
document.addEventListener('scroll', onScroll, {
315+
passive: false,
316+
});
281317
}
282318
});
283319

284-
// set current scroll position to bottom
285-
const scrollToBottom = () => {
286-
if (shepherd.value) {
287-
const offset =
288-
shepherd.value[isHorizontal.value ? 'offsetLeft' : 'offsetTop'];
289-
scrollToOffset(offset);
290-
291-
// check if it's really scrolled to the bottom
292-
// maybe list doesn't render and calculate to last range
293-
// so we need retry in next event loop until it really at bottom
294-
setTimeout(() => {
295-
if (getOffset() + getClientSize() < getScrollSize()) {
296-
scrollToBottom();
297-
}
298-
}, 3);
320+
onUnmounted(() => {
321+
virtual.destroy();
322+
if (props.pageMode) {
323+
document.removeEventListener('scroll', onScroll);
299324
}
300-
};
301-
302-
// get the total number of stored (rendered) items
303-
const getSizes = () => {
304-
return virtual.sizes.size;
305-
};
306-
307-
// get item size by id
308-
const getSize = (id) => {
309-
return virtual.sizes.get(id);
310-
};
325+
});
311326

327+
/**
328+
* public methods
329+
*/
312330
expose({
313331
scrollToBottom,
314332
getSizes,
@@ -333,9 +351,9 @@ export default defineComponent({
333351
footerClass,
334352
footerStyle,
335353
} = props;
336-
const { padFront, padBehind } = range.value;
354+
const { padFront, padBehind } = range.value!;
337355
const paddingStyle = {
338-
padding: isHorizontal.value
356+
padding: isHorizontal
339357
? `0px ${padBehind}px 0px ${padFront}px`
340358
: `${padFront}px 0px ${padBehind}px`,
341359
};
@@ -354,6 +372,7 @@ export default defineComponent({
354372
tag={headerTag}
355373
event={EVENT_TYPE.SLOT}
356374
uniqueKey={SLOT_TYPE.HEADER}
375+
onSlotResize={onSlotResized}
357376
>
358377
{header()}
359378
</Slot>
@@ -372,6 +391,7 @@ export default defineComponent({
372391
tag={footerTag}
373392
event={EVENT_TYPE.SLOT}
374393
uniqueKey={SLOT_TYPE.FOOTER}
394+
onSlotResize={onSlotResized}
375395
>
376396
{footer()}
377397
</Slot>
@@ -381,8 +401,8 @@ export default defineComponent({
381401
<div
382402
ref={shepherd}
383403
style={{
384-
width: isHorizontal.value ? '0px' : '100%',
385-
height: isHorizontal.value ? '100%' : '0px',
404+
width: isHorizontal ? '0px' : '100%',
405+
height: isHorizontal ? '100%' : '0px',
386406
}}
387407
/>
388408
</RootTag>

0 commit comments

Comments
 (0)