Skip to content

Commit fcd722f

Browse files
authored
fix: scroll manager resize for relative scroll pos (#588)
* fix: correct scroll for renderComplete * fix: scroll manager resize for relative scroll pos * chore: add comments for Infinite * docs: add comment for resizeScroll * chore: update `@egjs/grid` version * test: test Infinite * test: test end cursor * test: fix test code
1 parent 91d0c07 commit fcd722f

File tree

8 files changed

+137
-114
lines changed

8 files changed

+137
-114
lines changed

packages/infinitegrid/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@egjs/infinitegrid",
3-
"version": "4.13.0-beta.8",
3+
"version": "4.12.0",
44
"description": "A module used to arrange elements including content infinitely according to grid type. With this module, you can implement various grids composed of different card elements whose sizes vary. It guarantees performance by maintaining the number of DOMs the module is handling under any circumstance",
55
"module": "dist/infinitegrid.esm.js",
66
"main": "dist/infinitegrid.cjs.js",
@@ -122,7 +122,7 @@
122122
"@cfcs/core": "^0.0.5",
123123
"@egjs/children-differ": "^1.0.1",
124124
"@egjs/component": "^3.0.0",
125-
"@egjs/grid": "1.17.0-beta.3",
125+
"@egjs/grid": "1.18.0",
126126
"@egjs/list-differ": "^1.0.0"
127127
}
128128
}

packages/infinitegrid/src/Infinite.ts

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@ import { diff } from "@egjs/list-differ";
33
import { DIRECTION } from "./consts";
44
import { findIndex, findLastIndex, getNextCursors, isFlatOutline } from "./utils";
55

6+
7+
// 파트의 가운데 부분을 중심으로 스크롤 하기 위한 함수
8+
function getCenterPosByParts(parts: InfiniteItemPart[]) {
9+
if (!parts.length) {
10+
return 0;
11+
}
12+
let minPos = Infinity;
13+
let maxPos = -Infinity;
14+
15+
parts.forEach((part) => {
16+
minPos = Math.min(minPos, part.pos);
17+
maxPos = Math.max(maxPos, part.pos + part.size);
18+
}, 0);
19+
20+
return (minPos + maxPos) / 2;
21+
}
22+
623
export interface OnInfiniteRequestAppend {
724
key?: string | number | undefined;
825
nextKey?: string | number | undefined;
@@ -470,52 +487,66 @@ export class Infinite extends Component<InfiniteEvents> {
470487
}
471488
return Math.max(0, ...items[length - 1].endOutline);
472489
}
473-
public getVisibleArea(scrollPos: number, direction = this.options.defaultDirection) {
474-
const isDirectionEnd = direction === DIRECTION.END;
490+
/**
491+
* 보이는 영역의 가운데를 기준으로 스크롤을 한다.
492+
*/
493+
public getVisibleAreaByParts(parts: InfiniteItemPart[]) {
494+
const nextParts = parts.map((part) => this.getItemPartByKey(part.key)).filter(Boolean);
495+
496+
if (!nextParts.length) {
497+
return null;
498+
}
499+
const centerPos = getCenterPosByParts(nextParts);
500+
501+
return {
502+
parts: nextParts,
503+
centerPos,
504+
};
505+
}
506+
/**
507+
* 스크롤 가운데 위치에 가장 가까운 요소들
508+
*/
509+
public getVisibleArea(scrollPos: number) {
510+
const centerScrollPos = scrollPos + this.size / 2;
475511
const visibleItems = this.getRenderedVisibleItems();
476512

477513
if (!visibleItems.length) {
478514
return null;
479515
}
480-
const visibleItem = visibleItems[isDirectionEnd ? 0 : length - 1];
481-
const itemPos = isDirectionEnd
482-
? Math.min(...visibleItem.startOutline)
483-
: Math.max(...visibleItem.endOutline);
484-
let pos = itemPos;
485-
let itemPart!: InfiniteItemPart;
516+
const minParts: Array<[number, InfiniteItemPart]> = [];
486517

487-
if (isDirectionEnd) {
488-
visibleItems.forEach((item) => {
489-
item.parts?.forEach((part) => {
490-
if (itemPart && itemPart.pos >= part.pos) {
491-
return;
492-
}
493-
if (pos < part.pos && part.pos <= scrollPos) {
494-
itemPart = part;
495-
pos = part.pos;
496-
}
497-
});
518+
visibleItems.forEach((item) => {
519+
item.parts?.forEach((part) => {
520+
const centerPos = part.pos + part.size / 2;
521+
const minDist = Math.abs(centerScrollPos - centerPos);
522+
523+
minParts.push([minDist, part]);
498524
});
499-
} else {
500-
visibleItems.forEach((item) => {
501-
item.parts?.forEach((part) => {
502-
const endPos = part.pos + part.size;
525+
});
503526

504-
if (itemPart && itemPart.pos + itemPart.size <= endPos) {
505-
return;
506-
}
527+
let maxOutlineLength = 0;
507528

508-
if (pos > endPos && endPos >= scrollPos) {
509-
itemPart = part;
510-
pos = endPos;
511-
}
512-
});
513-
});
529+
530+
visibleItems.forEach((item) => {
531+
maxOutlineLength = Math.max(maxOutlineLength, item.startOutline.length);
532+
});
533+
534+
if (!maxOutlineLength) {
535+
return null;
536+
}
537+
538+
const visibleParts = minParts.sort(([minPos1], [minPos2]) => {
539+
return minPos1 - minPos2;
540+
}).slice(0, maxOutlineLength).map(([, part]) => part);
541+
542+
if (!visibleParts.length) {
543+
return null;
514544
}
545+
const centerPos = getCenterPosByParts(visibleParts);
515546

516547
return {
517-
item: visibleItem,
518-
part: itemPart,
548+
parts: visibleParts,
549+
centerPos,
519550
};
520551
}
521552
public getRenderedVisibleItems() {

packages/infinitegrid/src/InfiniteGrid.ts

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import {
2222
import { GroupManager } from "./GroupManager";
2323
import {
2424
Infinite,
25-
InfiniteItem,
26-
InfiniteItemPart,
2725
OnInfiniteChange,
2826
OnInfiniteRequestAppend,
2927
OnInfiniteRequestPrepend,
@@ -630,7 +628,7 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
630628
return !!this._waitType;
631629
}
632630
/**
633-
* <ko>scrollOffset(startOffset) 또는 scrollSize의 사이즈를 수동으로 업데이트 한다. 변경이 됐다면 스크롤이 발생시킨다.</ko>
631+
* <ko>scrollOffset(startOffset) 또는 scrollSize와 scrollPos를 수동으로 업데이트 한다. 변경이 된 경우 스크롤이 발생시킨다.</ko>
634632
*/
635633
public resizeScroll() {
636634
const result = this._resizeScroll();
@@ -919,43 +917,17 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
919917
const orgScrollPos = scrollManager.getScrollPos()!;
920918
const prevScrollSize = infinite.getScrollSize();
921919
const prevContainerSize = infinite.getSize();
922-
const prevVisibleArea = infinite.getVisibleArea(scrollPos, direction);
923-
const isDirectionEnd = direction === DIRECTION.END;
924-
920+
const prevVisibleArea = infinite.getVisibleArea(scrollPos);
925921

926922

927923
this._syncInfinite();
928924

929925
if (prevVisibleArea) {
930-
const prevPart = prevVisibleArea.part;
931-
const prevItem = prevVisibleArea.item;
932-
let nextPart!: InfiniteItemPart;
933-
let nextItem!: InfiniteItem;
926+
const prevParts = prevVisibleArea.parts;
927+
const nextVisibleArea = infinite.getVisibleAreaByParts(prevParts);
934928

935-
if (prevPart) {
936-
nextPart = infinite.getItemPartByKey(prevPart.key);
937-
}
938-
if (prevItem) {
939-
nextItem = infinite.getItemByKey(prevItem.key);
940-
}
941-
942-
if (nextPart || nextItem) {
943-
let prevPos = 0;
944-
let nextPos = 0;
945-
946-
if (nextPart) {
947-
nextPos = nextPart.pos + (isDirectionEnd ? 0 : nextPart.size);
948-
prevPos = prevPart.pos + (isDirectionEnd ? 0 : prevPart.size);
949-
} else {
950-
const prevStartPos = Math.min(...prevItem.startOutline);
951-
const prevEndPos = Math.max(...prevItem.endOutline);
952-
const nextStartPos = Math.min(...nextItem.startOutline);
953-
const nextEndPos = Math.max(...nextItem.endOutline);
954-
955-
nextPos = isDirectionEnd ? nextStartPos : nextEndPos;
956-
prevPos = isDirectionEnd ? prevStartPos : prevEndPos;
957-
}
958-
let offset = nextPos - prevPos;
929+
if (nextVisibleArea) {
930+
let offset = nextVisibleArea.centerPos - prevVisibleArea.centerPos;
959931

960932
// If reversed, scroll size (case where container size is reduced)
961933
const nextScrollSize = infinite.getScrollSize();

packages/infinitegrid/src/ScrollManager.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,13 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
142142
: scrollContainer.getBoundingClientRect();
143143
const containerRect = this.container.getBoundingClientRect();
144144

145-
const prevScrollOffset = this.scrollOffset;
145+
const prevRelativeScrollPos = this.getRelativeScrollPos();
146146
const prevContentSize = this.contentSize;
147+
const nextScrollPos = (this.getOrgScrollPos()! || 0);
147148

148-
this.scrollOffset = (this.getOrgScrollPos()! || 0) + (horizontal
149+
150+
this.setScrollPos(nextScrollPos);
151+
this.scrollOffset = nextScrollPos + (horizontal
149152
? containerRect.left - scrollContainerRect.left
150153
: containerRect.top - scrollContainerRect.top);
151154

@@ -155,7 +158,10 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
155158
this.contentSize = horizontal ? scrollContainer.offsetWidth : scrollContainer.offsetHeight;
156159
}
157160

158-
return prevScrollOffset !== this.scrollOffset || prevContentSize !== this.contentSize;
161+
const nextRelativeScrollPos = this.getRelativeScrollPos();
162+
163+
// 상대 scroll pos가 같다면 현재 화면에 보이는 아이템의 위치가 같기 때문에 업데이트할 필요가 없다.
164+
return nextRelativeScrollPos !== prevRelativeScrollPos || prevContentSize !== this.contentSize;
159165
}
160166
public destroy() {
161167
const container = this.container;
@@ -229,7 +235,6 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
229235
this.scrollContainer = scrollContainer;
230236
this.eventTarget = eventTarget;
231237
this.resize();
232-
this.setScrollPos(this.getOrgScrollPos());
233238
}
234239
private _onCheck = () => {
235240
const prevScrollPos = this.getScrollPos();

packages/infinitegrid/test/unit/Infinite.spec.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,17 @@ describe("test Infinite", () => {
8686
{
8787
key: 1,
8888
pos: 0,
89-
size: 1,
89+
size: 100,
9090
},
9191
{
9292
key: 2,
9393
pos: 100,
94-
size: 1,
94+
size: 100,
9595
},
9696
{
9797
key: 3,
9898
pos: 200,
99-
size: 1,
99+
size: 100,
100100
},
101101
],
102102
},
@@ -108,17 +108,17 @@ describe("test Infinite", () => {
108108
{
109109
key: 4,
110110
pos: 300,
111-
size: 1,
111+
size: 100,
112112
},
113113
{
114114
key: 5,
115115
pos: 400,
116-
size: 1,
116+
size: 100,
117117
},
118118
{
119119
key: 6,
120120
pos: 500,
121-
size: 1,
121+
size: 100,
122122
},
123123
],
124124
},
@@ -130,17 +130,17 @@ describe("test Infinite", () => {
130130
{
131131
key: 7,
132132
pos: 600,
133-
size: 1,
133+
size: 100,
134134
},
135135
{
136136
key: 8,
137137
pos: 700,
138-
size: 1,
138+
size: 100,
139139
},
140140
{
141141
key: 9,
142142
pos: 800,
143-
size: 1,
143+
size: 100,
144144
},
145145
],
146146
},
@@ -166,43 +166,50 @@ describe("test Infinite", () => {
166166
const area11 = infinite.getVisibleArea(600);
167167
const area12 = infinite.getVisibleArea(800);
168168

169+
// parts는 아웃라인 수 만큼 나오고 가장 가까운 대상이 key로 나온다.
170+
// 250과 가장 가까운 part
171+
expect(area1!.parts[0].key).to.be.deep.equals(3);
172+
expect(area1!.centerPos).to.be.deep.equals(250);
169173

170174
// Then
171-
expect(area1!.item.key).to.be.deep.equals(1);
172-
expect(area1!.part.key).to.be.deep.equals(2);
173175

174-
expect(area2!.item.key).to.be.deep.equals(1);
175-
expect(area2!.part.key).to.be.deep.equals(3);
176+
expect(area2!.parts[0].key).to.be.deep.equals(3);
177+
expect(area2!.centerPos).to.be.deep.equals(250);
176178

177-
expect(area3!.item.key).to.be.deep.equals(1);
178-
expect(area3!.part.key).to.be.deep.equals(2);
179179

180-
expect(area4!.item.key).to.be.deep.equals(1);
181-
expect(area4!.part.key).to.be.deep.equals(3);
180+
expect(area3!.parts[0].key).to.be.deep.equals(3);
181+
expect(area3!.centerPos).to.be.deep.equals(250);
182182

183-
expect(area5!.item.key).to.be.deep.equals(1);
184-
expect(area5!.part.key).to.be.deep.equals(5);
183+
expect(area4!.parts[0].key).to.be.deep.equals(5);
184+
expect(area4!.centerPos).to.be.deep.equals(450);
185185

186-
expect(area6!.item.key).to.be.deep.equals(1);
187-
expect(area6!.part.key).to.be.deep.equals(6);
186+
expect(area5!.parts[0].key).to.be.deep.equals(6);
187+
expect(area5!.centerPos).to.be.deep.equals(550);
188188

189-
expect(area7!.item.key).to.be.deep.equals(2);
190-
expect(area7!.part).to.be.not.ok;
191189

192-
expect(area8!.item.key).to.be.deep.equals(2);
193-
expect(area8!.part).to.be.not.ok;
190+
expect(area6!.parts[0].key).to.be.deep.equals(6);
191+
expect(area6!.centerPos).to.be.deep.equals(550);
194192

195-
expect(area9!.item.key).to.be.deep.equals(2);
196-
expect(area9!.part.key).to.be.deep.equals(5);
193+
expect(area7!.parts[0].key).to.be.deep.equals(4);
194+
expect(area7!.centerPos).to.be.deep.equals(350);
197195

198-
expect(area10!.item.key).to.be.deep.equals(2);
199-
expect(area10!.part.key).to.be.deep.equals(6);
200196

201-
expect(area11!.item.key).to.be.deep.equals(2);
202-
expect(area11!.part.key).to.be.deep.equals(7);
197+
expect(area8!.parts[0].key).to.be.deep.equals(4);
198+
expect(area8!.centerPos).to.be.deep.equals(350);
203199

204-
expect(area12!.item.key).to.be.deep.equals(2);
205-
expect(area12!.part.key).to.be.deep.equals(9);
200+
expect(area9!.parts[0].key).to.be.deep.equals(6);
201+
expect(area9!.centerPos).to.be.deep.equals(550);
202+
203+
204+
205+
expect(area10!.parts[0].key).to.be.deep.equals(8);
206+
expect(area10!.centerPos).to.be.deep.equals(750);
207+
208+
expect(area11!.parts[0].key).to.be.deep.equals(8);
209+
expect(area11!.centerPos).to.be.deep.equals(750);
210+
211+
expect(area12!.parts[0].key).to.be.deep.equals(9);
212+
expect(area12!.centerPos).to.be.deep.equals(850);
206213
});
207214
it("should check if the cursor changes when you sync items", () => {
208215
infinite = new Infinite({});

0 commit comments

Comments
 (0)