Skip to content

Commit 0c5a205

Browse files
committed
done of realtive top
1 parent 18987fa commit 0c5a205

File tree

3 files changed

+157
-104
lines changed

3 files changed

+157
-104
lines changed

examples/animate.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ const Demo = () => {
123123
itemHeight={30}
124124
itemKey="id"
125125
style={{
126-
border: '1px solid red',
127-
boxSizing: 'border-box',
126+
// border: '1px solid red',
127+
// boxSizing: 'border-box',
128+
boxShadow: '0 0 2px red',
128129
}}
129130
>
130131
{item => (

src/List.tsx

Lines changed: 98 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import {
55
getScrollPercentage,
66
getNodeHeight,
77
getRangeIndex,
8-
getItemTop,
8+
getItemAbsoluteTop,
99
GHOST_ITEM_KEY,
10+
getItemRelativeTop,
11+
getCompareItemRelativeTop,
1012
} from './util';
1113

1214
type RenderFunc<T> = (item: T) => React.ReactNode;
@@ -115,7 +117,7 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
115117
}
116118

117119
// Calculate top visible item top offset
118-
const locatedItemTop = getItemTop({
120+
const locatedItemTop = getItemAbsoluteTop({
119121
itemIndex,
120122
itemOffsetPtg,
121123
itemElementHeights: this.itemElementHeights,
@@ -133,65 +135,73 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
133135
this.setState({ status: 'MEASURE_DONE', startItemTop });
134136
}
135137

136-
// TODO: If is add node
137-
// Re-calculate the scroll position align with the current visible item position
138+
/**
139+
* Re-calculate the item position since `dataSource` length changed.
140+
* [IMPORTANT] We use relative position calculate here.
141+
*/
138142
if (prevProps.dataSource.length !== dataSource.length && height) {
139-
/**
140-
* When an item removed,
141-
* we should know the item (called as compare item) before the removed item position.
142-
* After loop re-calculation, we need keep the compare item position not change.
143-
*/
144-
// Find removed item key
143+
const {
144+
itemIndex: originItemIndex,
145+
itemOffsetPtg: originItemOffsetPtg,
146+
startIndex: originStartIndex,
147+
endIndex: originEndIndex,
148+
} = this.state;
149+
150+
// 1. Get origin located item top
151+
const originLocatedItemRelativeTop = getItemRelativeTop({
152+
itemIndex: originItemIndex,
153+
itemOffsetPtg: originItemOffsetPtg,
154+
itemElementHeights: this.itemElementHeights,
155+
scrollPtg: getElementScrollPercentage(this.listRef.current),
156+
clientHeight: this.listRef.current.clientHeight,
157+
getItemKey: (index: number) => this.getItemKey(index, prevProps),
158+
});
159+
160+
console.log(
161+
'1. Origin Located:',
162+
originItemIndex,
163+
originItemOffsetPtg,
164+
this.getItemKey(originItemIndex, prevProps),
165+
originLocatedItemRelativeTop,
166+
);
167+
168+
// 2. Find the compare item
145169
const removedItemIndex: number = prevProps.dataSource.findIndex((_, index) => {
146170
const key = this.getItemKey(index, prevProps);
147171
return dataSource.every((__, nextIndex) => key !== this.getItemKey(nextIndex));
148172
});
149-
150-
// We use the item before removed item as base compare position to enhance the accuracy
151-
const { startIndex: originStartIndex, itemIndex: originItemIndex } = this.state;
152-
153173
let originCompareItemIndex = removedItemIndex - 1;
154-
if (originCompareItemIndex >= originItemIndex) originCompareItemIndex = originItemIndex;
155-
156-
// Find compare item key & offset top
157-
let compareItemIndex: number;
158-
let compareItemKey: string;
159-
let originCompareItemTop = this.state.startItemTop;
174+
// Use next one since there are not more item before removed
160175
if (originCompareItemIndex < 0) {
161-
// If remove item is the first one, we have compare next one
162176
originCompareItemIndex = 0;
163-
compareItemIndex = 0;
164-
compareItemKey = this.getItemKey(compareItemIndex);
165-
} else {
166-
// If exist compare item
167-
compareItemKey = this.getItemKey(originCompareItemIndex, prevProps);
168-
169-
for (let index = originStartIndex; index <= originItemIndex; index += 1) {
170-
const key = this.getItemKey(index, prevProps);
171-
if (key === compareItemKey) {
172-
break;
173-
}
174-
175-
originCompareItemTop += this.itemElementHeights[key] || 0;
176-
}
177-
178-
// Find current compare item index
179-
compareItemIndex = dataSource.findIndex(
180-
(_, index) => this.getItemKey(index) === compareItemKey,
181-
);
182177
}
178+
const compareItemKey = this.getItemKey(originCompareItemIndex, prevProps);
179+
console.log('2. Compare Item:', compareItemKey);
180+
181+
// 3. Find the compare item top
182+
const originCompareItemTop = getCompareItemRelativeTop({
183+
locatedItemRelativeTop: originLocatedItemRelativeTop,
184+
locatedItemIndex: originItemIndex,
185+
compareItemIndex: originCompareItemIndex,
186+
startIndex: originStartIndex,
187+
endIndex: originEndIndex,
188+
getItemKey: (index: number) => this.getItemKey(index, prevProps),
189+
itemElementHeights: this.itemElementHeights,
190+
});
183191

184-
// Loop to generate compared item top and find best one
185-
const { scrollHeight, clientHeight } = this.listRef.current;
186-
const maxScrollTop = scrollHeight - clientHeight;
192+
console.log('3. Compare Item Top:', originCompareItemTop);
187193

194+
// 4. Find the best match compare item top
195+
let bestSimilarity = Number.MAX_VALUE;
188196
let bestScrollTop: number = null;
189-
let bestSimilarity: number = Number.MAX_VALUE;
190-
let bestItemIndex = 0;
191-
let bestItemOffsetPtg = 0;
192-
let bestStartIndex = 0;
193-
let bestEndIndex = 0;
197+
let bestItemIndex: number = null;
198+
let bestItemOffsetPtg: number = null;
199+
let bestStartIndex: number = null;
200+
let bestEndIndex: number = null;
194201

202+
const scrollHeight = dataSource.length * itemHeight;
203+
const { clientHeight } = this.listRef.current;
204+
const maxScrollTop = scrollHeight - clientHeight;
195205
for (let scrollTop = 0; scrollTop < maxScrollTop; scrollTop += 1) {
196206
const scrollPtg = getScrollPercentage({ scrollTop, scrollHeight, clientHeight });
197207
const visibleCount = Math.ceil(height / itemHeight);
@@ -202,66 +212,59 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
202212
visibleCount,
203213
);
204214

205-
/**
206-
* No need to check if compare item out of the index
207-
* to save performance.
208-
*/
209-
if (startIndex <= compareItemIndex && compareItemIndex <= endIndex) {
210-
let locatedItemTop = getItemTop({
215+
// No need to check if compare item out of the index to save performance
216+
if (startIndex <= originCompareItemIndex && originCompareItemIndex <= endIndex) {
217+
// 4.1 Get measure located item relative top
218+
const locatedItemRelativeTop = getItemRelativeTop({
211219
itemIndex,
212220
itemOffsetPtg,
213221
itemElementHeights: this.itemElementHeights,
214-
scrollTop: this.listRef.current.scrollTop,
215222
scrollPtg,
216-
clientHeight: this.listRef.current.clientHeight,
223+
clientHeight,
217224
getItemKey: this.getItemKey,
218225
});
219226

220-
let newCompareItemTop: number = null;
221-
if (compareItemIndex <= itemIndex) {
222-
// Measure if compare item is before located item
223-
for (let index = itemIndex; index >= startIndex; index -= 1) {
224-
const key = this.getItemKey(index);
225-
if (key === compareItemKey) {
226-
newCompareItemTop = locatedItemTop;
227-
break;
228-
}
229-
230-
if (index <= 0) {
231-
break;
232-
}
233-
234-
const prevItemKey = this.getItemKey(index - 1);
235-
locatedItemTop -= this.itemElementHeights[prevItemKey] || 0;
236-
}
237-
} else {
238-
// Measure if compare item is after located item
239-
for (let index = itemIndex; index <= endIndex; index += 1) {
240-
const key = this.getItemKey(index);
241-
locatedItemTop += this.itemElementHeights[key] || 0;
242-
if (key === compareItemKey) {
243-
newCompareItemTop = locatedItemTop;
244-
break;
245-
}
246-
}
247-
}
227+
const compareItemTop = getCompareItemRelativeTop({
228+
locatedItemRelativeTop,
229+
locatedItemIndex: itemIndex,
230+
compareItemIndex: originCompareItemIndex, // Same as origin index
231+
startIndex,
232+
endIndex,
233+
getItemKey: this.getItemKey,
234+
itemElementHeights: this.itemElementHeights,
235+
});
236+
237+
// console.log('4.1 ScrollTop:', scrollTop, 'CompareTop:', compareItemTop);
248238

249-
if (newCompareItemTop !== null) {
250-
const similarity = Math.abs(newCompareItemTop - originCompareItemTop);
251-
if (similarity < bestSimilarity) {
252-
bestScrollTop = scrollTop;
253-
bestSimilarity = similarity;
254-
bestItemIndex = itemIndex;
255-
bestItemOffsetPtg = itemOffsetPtg;
256-
bestStartIndex = startIndex;
257-
bestEndIndex = endIndex;
258-
}
239+
// 4.2 Find best match compare item top
240+
const similarity = Math.abs(compareItemTop - originCompareItemTop);
241+
if (similarity < bestSimilarity) {
242+
console.log('4.2 Winner:', scrollTop, compareItemTop.toFixed(2));
243+
console.log(' -> ', similarity.toFixed(2), bestSimilarity.toFixed(2));
244+
245+
bestSimilarity = similarity;
246+
bestScrollTop = scrollTop;
247+
bestItemIndex = itemIndex;
248+
bestItemOffsetPtg = itemOffsetPtg;
249+
bestStartIndex = startIndex;
250+
bestEndIndex = endIndex;
259251
}
260252
}
261253
}
262254

263-
// Update `scrollTop` to new calculated position
255+
// 5. Re-scroll if has best scroll match
264256
if (bestScrollTop !== null) {
257+
console.log(
258+
'5. Find best match:',
259+
bestScrollTop,
260+
'Located:',
261+
bestItemIndex,
262+
bestItemOffsetPtg,
263+
'Range:',
264+
bestStartIndex,
265+
bestEndIndex,
266+
);
267+
265268
this.lockScroll = true;
266269
this.listRef.current.scrollTop = bestScrollTop;
267270

src/util.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,71 @@ interface ItemTopConfig {
9999
}
100100

101101
/**
102-
* Calculate the located item top.
102+
* Calculate the located item related top with current window height
103103
*/
104-
export function getItemTop({
104+
export function getItemRelativeTop({
105105
itemIndex,
106106
itemOffsetPtg,
107107
itemElementHeights,
108-
scrollTop,
109108
scrollPtg,
110109
clientHeight,
111110
getItemKey,
112-
}: ItemTopConfig) {
113-
// Calculate top visible item top offset
111+
}: Omit<ItemTopConfig, 'scrollTop'>) {
114112
const locatedItemHeight = itemElementHeights[getItemKey(itemIndex)] || 0;
115113
const locatedItemTop = scrollPtg * clientHeight;
116114
const locatedItemOffset = itemOffsetPtg * locatedItemHeight;
117-
const locatedItemMergedTop = scrollTop + locatedItemTop - locatedItemOffset;
115+
return locatedItemTop - locatedItemOffset;
116+
}
117+
118+
/**
119+
* Calculate the located item absolute top with whole scroll height
120+
*/
121+
export function getItemAbsoluteTop({ scrollTop, ...rest }: ItemTopConfig) {
122+
return scrollTop + getItemRelativeTop(rest);
123+
}
124+
125+
interface CompareItemConfig {
126+
locatedItemRelativeTop: number;
127+
locatedItemIndex: number;
128+
compareItemIndex: number;
129+
getItemKey: (index: number) => string;
130+
startIndex: number;
131+
endIndex: number;
132+
itemElementHeights: { [key: string]: number };
133+
}
134+
135+
export function getCompareItemRelativeTop({
136+
locatedItemRelativeTop,
137+
locatedItemIndex,
138+
compareItemIndex,
139+
startIndex,
140+
endIndex,
141+
getItemKey,
142+
itemElementHeights,
143+
}: CompareItemConfig) {
144+
let originCompareItemTop: number = locatedItemRelativeTop;
145+
const compareItemKey = getItemKey(compareItemIndex);
146+
147+
if (compareItemIndex <= locatedItemIndex) {
148+
for (let index = locatedItemIndex; index >= startIndex; index -= 1) {
149+
const key = getItemKey(index);
150+
if (key === compareItemKey) {
151+
break;
152+
}
153+
154+
const prevItemKey = getItemKey(index - 1);
155+
originCompareItemTop -= itemElementHeights[prevItemKey] || 0;
156+
}
157+
} else {
158+
for (let index = locatedItemIndex; index <= endIndex; index += 1) {
159+
const key = getItemKey(index);
160+
if (key === compareItemKey) {
161+
break;
162+
}
163+
164+
originCompareItemTop += itemElementHeights[key] || 0;
165+
}
166+
}
118167

119-
return locatedItemMergedTop;
168+
return originCompareItemTop;
120169
}

0 commit comments

Comments
 (0)