Skip to content
52 changes: 41 additions & 11 deletions packages/infinitegrid/src/Infinite.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Component from "@egjs/component";
import { diff } from "@egjs/list-differ";
import { DIRECTION } from "./consts";
import { DIRECTION, INVISIBLE_POS } from "./consts";
import { findIndex, findLastIndex, getNextCursors, isFlatOutline } from "./utils";


Expand Down Expand Up @@ -466,12 +466,18 @@ export class Infinite extends Component<InfiniteEvents> {
return this.itemKeys[key];
}
public getItemPartByKey(partKey: string | number) {
let itemPart!: InfiniteItemPart;
let itemPart!: {
itemIndex: number;
part: InfiniteItemPart;
};

this.items.forEach((item) => {
this.items.forEach((item, itemIndex) => {
item.parts?.forEach((part) => {
if (part.key === partKey) {
itemPart = part;
itemPart = {
itemIndex: itemIndex,
part,
};
}
});
});
Expand All @@ -491,36 +497,55 @@ export class Infinite extends Component<InfiniteEvents> {
* 보이는 영역의 가운데를 기준으로 스크롤을 한다.
*/
public getVisibleAreaByParts(parts: InfiniteItemPart[]) {
const nextParts = parts.map((part) => this.getItemPartByKey(part.key)).filter(Boolean);
const nextPartInfos = parts.map((part) => this.getItemPartByKey(part.key))
.filter(Boolean);

if (!nextParts.length) {
if (!nextPartInfos.length) {
return null;
}

let startCursor = Infinity;
let endCursor = -1;

nextPartInfos.forEach((part) => {
startCursor = Math.min(part.itemIndex, startCursor);
endCursor = Math.min(part.itemIndex, endCursor);
});
const nextParts = nextPartInfos.map(({ part }) => part);
const centerPos = getCenterPosByParts(nextParts);

return {
parts: nextParts,
centerPos,
startCursor,
endCursor,
};
}
/**
* 스크롤 가운데 위치에 가장 가까운 요소들
*/
public getVisibleArea(scrollPos: number) {
const items = this.items;
const centerScrollPos = scrollPos + this.size / 2;
const visibleItems = this.getRenderedVisibleItems();

if (!visibleItems.length) {
return null;
}
const minParts: Array<[number, InfiniteItemPart]> = [];
const minParts: Array<[number, {
itemIndex: number;
part: InfiniteItemPart;
}]> = [];

visibleItems.forEach((item) => {
item.parts?.forEach((part) => {
item.parts?.filter((p) => p.pos !== INVISIBLE_POS).forEach((part) => {
const centerPos = part.pos + part.size / 2;
const minDist = Math.abs(centerScrollPos - centerPos);

minParts.push([minDist, part]);
minParts.push([minDist, {
part,
itemIndex: items.findIndex((allItem) => allItem.key === item.key),
}]);
});
});

Expand All @@ -535,16 +560,21 @@ export class Infinite extends Component<InfiniteEvents> {
return null;
}

const visibleParts = minParts.sort(([minPos1], [minPos2]) => {
const visiblePartInfos = minParts.sort(([minPos1], [minPos2]) => {
return minPos1 - minPos2;
}).slice(0, maxOutlineLength).map(([, part]) => part);

if (!visibleParts.length) {
if (!visiblePartInfos.length) {
return null;
}

const visibleParts = visiblePartInfos.map(({ part }) => part);
const centerPos = getCenterPosByParts(visibleParts);
const centerIndexes = visiblePartInfos.map((part) => part.itemIndex);

return {
centerStartIndex: Math.min(...centerIndexes),
centerEndIndex: Math.max(...centerIndexes),
parts: visibleParts,
centerPos,
};
Expand Down
11 changes: 9 additions & 2 deletions packages/infinitegrid/src/InfiniteGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,17 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
this._syncInfinite();

if (prevVisibleArea) {
const prevParts = prevVisibleArea.parts;
// 화면의 가운데가 어디에 위치해있는지 확인
const prevParts = prevVisibleArea.parts.filter((p) => p.pos !== INVISIBLE_POS);
const nextVisibleArea = infinite.getVisibleAreaByParts(prevParts);

if (nextVisibleArea) {
if (
nextVisibleArea
// 커서가 시작이 아니어야 그룹들의 위치 보정이 가능하다.
// end direction만 해당
// startCursor가 0이면 위의 아이템들의 위치가 심각하게 흔들릴 가능성이 매우 높다.
&& (direction !== "end" || prevVisibleArea.centerStartIndex !== 0)
) {
let offset = nextVisibleArea.centerPos - prevVisibleArea.centerPos;

// If reversed, scroll size (case where container size is reduced)
Expand Down
125 changes: 112 additions & 13 deletions packages/infinitegrid/test/unit/InfiniteGrid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ describe("test InfiniteGrid", () => {

const children = toArray(igContainer.children);

ig?.getItems().forEach(itm => {
console.log(itm.cssRect, itm.mountState, itm.updateState);
})
// Then
expect(e.startCursor).to.be.equals(0);
expect(e.endCursor).to.be.equals(1);
Expand Down Expand Up @@ -422,19 +419,18 @@ describe("test InfiniteGrid", () => {
ig!.append([0, 1, 2, 3, 4].map((child) => {
return {
groupKey: Math.floor(child / 5),
key: child,
html: `<div style="height: 200px;">${child}</div>`,
key: `${child}`,
html: `<div style="height: 200px;width: 2px;">${child}</div>`,
};
}));

await waitEvent(ig!, "renderComplete");


ig!.prepend([5, 6, 7, 8, 9].map((child) => {
return {
groupKey: Math.floor(child / 5),
key: child,
html: `<div style="height: 200px;">${child}</div>`,
html: `<div style="height: 200px;width: 2px;">${child}</div>`,
};
}));

Expand All @@ -448,9 +444,8 @@ describe("test InfiniteGrid", () => {
expect(ig!.getVisibleGroups().map((group) => group.groupKey)).to.be.deep.equals([1, 0]);
expect(ig!.getScrollContainerElement().scrollTop).to.be.equals(1000);
expect(children.length).to.be.equals(10);

children.forEach((child, i) => {
expect(child.style.top).to.be.equals(`${i * 200}px`);
expect(child.style.top).to.be.equals(`${i * 200}px`, `Index ${i} Error`);
});
});
it("should check if item can be inserted in the center", async () => {
Expand Down Expand Up @@ -1280,7 +1275,7 @@ describe("test InfiniteGrid", () => {
await waitEvent(ig!, "renderComplete");
// 500 ~ 1000
// 300 / 100 100 [100 / 300 / 300 / 300] / 300 / 300


// Then
expect(ig!.getScrollContainerElement().scrollTop).to.be.equals(500);
Expand Down Expand Up @@ -1589,6 +1584,111 @@ describe("test InfiniteGrid", () => {
});
});
});
describe("test scroll offset", () => {
beforeEach(() => {
container!.innerHTML = `
<div class="wrapper" style="width: 100%; height: 500px;">
</div>
`;
const wrapper = container!.querySelector<HTMLElement>(".wrapper")!;
ig = new InfiniteGrid<InfiniteGridOptions>(wrapper, {
gridConstructor: SampleGrid,
container: true,
threshold: 0,
});
});
it(`should check if scroll position is corrected when size, pos, window size is changed (startCursor = 0)`, async () => {
// Given
ig!.syncItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].map((child) => {
return {
groupKey: Math.floor(child / 3),
key: `key${child}`,
html: `<div style="height: 100px;width: 100%; ">${child}</div>`,
};
}));

// 커서를 전체로 지정하여 모든 아이템의 사이즈를 계산
ig!.setCursors(0, 5);
await waitEvent(ig!, "renderComplete");

// 현재 스크롤 위치에 따른 보이는 아이템 자동 변경: change cursor (0, 2)
await waitEvent(ig!, "renderComplete");


// top 100, center 350
// 브라우저간의 오차로 인한 테스트가 필요
ig!.getScrollContainerElement().scrollTop = 100;

// 스크롤 이동에 따른 보이는 아이템 자동 변경: change cursor (0, 3)
await waitEvent(ig!, "renderComplete");
// 300 + 50 => 700 (offset: 350)


// When
ig!.getItems().forEach((item) => {
item.element!.style.height = "200px";
});

// 스크롤 위치는 변경 되지 않는다.
ig!.renderItems({ useResize: true });
await waitEvent(ig!, "renderComplete");


// Then
const correctedPos = ig!.getScrollContainerElement().scrollTop;
expect(correctedPos).to.be.equals(450);
expect(ig!.getStartCursor()).to.be.equals(0);
expect(ig!.getEndCursor()).to.be.equals(1);
});
it(`should check if scroll position is corrected when size, pos, window size is changed (startCursor > 0)`, async () => {
// Given
ig!.syncItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].map((child) => {
return {
groupKey: Math.floor(child / 3),
key: `key${child}`,
html: `<div style="height: 100px;width: 100%;">${child}</div>`,
};
}));

// 커서를 전체로 지정하여 모든 아이템의 사이즈를 계산
ig!.setCursors(0, 5);
await waitEvent(ig!, "renderComplete");

// 현재 스크롤 위치에 따른 보이는 아이템 자동 변경: change cursor (0, 2)
await waitEvent(ig!, "renderComplete");


// top: 750, center 1000
ig!.getScrollContainerElement().scrollTop = 750;

// 스크롤 이동에 따른 보이는 아이템 자동 변경: change cursor (2, 4)
await waitEvent(ig!, "renderComplete");
const prevStartCursor = ig!.getStartCursor();
const prevEndCursor = ig!.getEndCursor();



// When
ig!.getItems().forEach((item) => {
item.element!.style.height = "200px";
});
// 600 + 300 + 50 => 600 + 700 (offset: 350)


// 스크롤 위치는 변경 되지 않는다.
// 450만큼 스크롤 차이가 발생
ig!.renderItems({ useResize: true });
await waitEvent(ig!, "renderComplete");

// Then
expect(prevStartCursor).to.be.equals(2);
expect(prevEndCursor).to.be.equals(4);
const correctedPos = ig!.getScrollContainerElement().scrollTop;
expect(correctedPos).to.be.equals(1100);
expect(ig!.getStartCursor()).to.be.equals(2);
expect(ig!.getEndCursor()).to.be.equals(3);
});
});
describe("test ResizeObserver", () => {
it(`should check if renderComplete does trigger when useResizeObserver is enabled and container's size is changed`, async () => {
// Given
Expand Down Expand Up @@ -1770,9 +1870,8 @@ describe("test InfiniteGrid", () => {
return {
groupKey: Math.floor(child / 3),
key: `key${child}`,
html: `<div style="height: ${100 + child * 10}px;width: 100%;" ${
child === 7 ? `data-grid-not-equal-size="true"` : ""
}>${child}</div>`,
html: `<div style="height: ${100 + child * 10}px;width: 100%;" ${child === 7 ? `data-grid-not-equal-size="true"` : ""
}>${child}</div>`,
};
}));

Expand Down
Loading