Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
132 changes: 119 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,118 @@ 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">${child}</div>`,
};
}));

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

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


// top 320, center는 570 5와 6 사이
// 브라우저간의 오차로 인한 테스트가 필요
ig!.getScrollContainerElement().scrollTop = 320;

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


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

// 6의 300 + 300 + 50 => 300 + 600 + 100
// 350 차이
// 스크롤 위치는 변경 되지 않는다.
ig!.renderItems({ useResize: true });
await waitEvent(ig!, "renderComplete");


// Then
const correctedPos = ig!.getScrollContainerElement().scrollTop;
expect(correctedPos).to.be.equals(670);
expect(ig!.getStartCursor()).to.be.equals(1);
expect(ig!.getEndCursor()).to.be.equals(2);
});
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">${child}</div>`,
};
}));

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

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


// 6의 300 + 300 + 50 => 300 + 600 + 100
// 350 차이
ig!.getScrollContainerElement().scrollTop = 750;

// 스크롤 이동에 따른 보이는 아이템 자동 변경: change cursor (2, 4)
// 6 ~ 14 9개
// 600 ~ 1500
// 중간값: 1050
await waitEvent(ig!, "renderComplete");
const prevStartCursor = ig!.getStartCursor();
const prevEndCursor = ig!.getEndCursor();



// When
// 6 ~ 14 9개
// 600 + 0 ~ 600 + 1800
// 중간값: 1500
ig!.getItems().forEach((item) => {
item.element!.style.height = "200px";
});


// 스크롤 위치는 변경 되지 않는다.
// 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(1200);
expect(ig!.getStartCursor()).to.be.equals(3);
expect(ig!.getEndCursor()).to.be.equals(4);
});
});
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 +1877,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