Skip to content

Commit 7fc15c7

Browse files
authored
feat: add resizeObservePanel option (#884)
* fix: apply ResizeObserver to camera and panels * fix: unobserve removed panel when panel change * fix: add defense code to check if element exists * feat: add resizeObservePanel option * fix: add defensive logic to getStyle * chore: change resizeObservePanel to resizePanelObserve * fix: add unobserve panel logic * chore: update setup-node to v4
1 parent 293da58 commit 7fc15c7

File tree

7 files changed

+167
-8
lines changed

7 files changed

+167
-8
lines changed

.github/workflows/run-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
runs-on: ubuntu-latest
66
steps:
77
- uses: actions/checkout@v2
8-
- uses: actions/setup-node@v2
8+
- uses: actions/setup-node@v4
99
with:
1010
node-version: 16
1111
cache: "npm"
@@ -30,7 +30,7 @@ jobs:
3030
runs-on: ubuntu-latest
3131
steps:
3232
- uses: actions/checkout@v2
33-
- uses: actions/setup-node@v2
33+
- uses: actions/setup-node@v4
3434
with:
3535
node-version: 16
3636
cache: "npm"

src/Flicking.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface FlickingOptions {
9191
autoResize: boolean;
9292
useResizeObserver: boolean;
9393
resizeDebounce: number;
94+
resizePanelObserve: boolean;
9495
maxResizeDebounce: number;
9596
useFractionalSize: boolean;
9697
externalRenderer: ExternalRenderer | null;
@@ -168,6 +169,7 @@ class Flicking extends Component<FlickingEvents> {
168169
private _autoResize: FlickingOptions["autoResize"];
169170
private _useResizeObserver: FlickingOptions["useResizeObserver"];
170171
private _resizeDebounce: FlickingOptions["resizeDebounce"];
172+
private _resizePanelObserve: FlickingOptions["resizePanelObserve"];
171173
private _maxResizeDebounce: FlickingOptions["maxResizeDebounce"];
172174
private _useFractionalSize: FlickingOptions["useFractionalSize"];
173175
private _externalRenderer: FlickingOptions["externalRenderer"];
@@ -220,6 +222,13 @@ class Flicking extends Component<FlickingEvents> {
220222
* @see Viewport
221223
*/
222224
public get viewport() { return this._viewport; }
225+
/**
226+
* {@link AutoResizer} instance of the Flicking
227+
* @ko 현재 Flicking에 활성화된 {@link AutoResizer} 인스턴스
228+
* @internal
229+
* @readonly
230+
*/
231+
public get autoResizer() { return this._autoResizer; }
223232
// Internal States
224233
/**
225234
* Whether Flicking's {@link Flicking#init init()} is called.
@@ -695,6 +704,15 @@ class Flicking extends Component<FlickingEvents> {
695704
* @see {@link https://naver.github.io/egjs-flicking/Options#useresizeobserver useResizeObserver ( Options )}
696705
*/
697706
public get useResizeObserver() { return this._useResizeObserver; }
707+
/**
708+
* Whether to use {@link https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver ResizeObserver} to observe the size of the panel element
709+
* This is only available when `useResizeObserver` is enabled.
710+
* This option garantees that the resize event is triggered when the size of the panel element is changed.
711+
* @ko 이 옵션을 활성화할 경우, {@link https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver ResizeObserver}를 사용하여 패널 엘리먼트의 크기를 추적합니다.
712+
* 이 옵션은 `useResizeObserver` 옵션이 활성화된 경우에만 사용할 수 있습니다.
713+
* 이 옵션은 패널 엘리먼트의 크기가 변경될 경우 resize 이벤트가 발생하도록 보장합니다.
714+
*/
715+
public get resizePanelObserve() { return this._resizePanelObserve; }
698716
/**
699717
* Delays size recalculation from `autoResize` by the given time in milisecond.
700718
* If the size is changed again while being delayed, it cancels the previous one and delays from the beginning again.
@@ -926,6 +944,10 @@ class Flicking extends Component<FlickingEvents> {
926944
public set autoResize(val: FlickingOptions["autoResize"]) {
927945
this._autoResize = val;
928946

947+
if (!this._initialized) {
948+
return;
949+
}
950+
929951
if (val) {
930952
this._autoResizer.enable();
931953
} else {
@@ -936,11 +958,23 @@ class Flicking extends Component<FlickingEvents> {
936958
public set useResizeObserver(val: FlickingOptions["useResizeObserver"]) {
937959
this._useResizeObserver = val;
938960

939-
if (this._autoResize) {
961+
if (this._initialized && this._autoResize) {
940962
this._autoResizer.enable();
941963
}
942964
}
943965

966+
public set resizePanelObserve (val: FlickingOptions["resizePanelObserve"]) {
967+
this._resizePanelObserve = val;
968+
969+
if (this._initialized && this._autoResize) {
970+
if (val) {
971+
this._autoResizer.observePanels();
972+
} else {
973+
this._autoResizer.unobservePanels();
974+
}
975+
}
976+
}
977+
944978
/**
945979
* @param root A root HTMLElement to initialize Flicking on it. When it's a typeof `string`, it should be a css selector string
946980
* <ko>Flicking을 초기화할 HTMLElement로, `string` 타입으로 지정시 css 선택자 문자열을 지정해야 합니다.</ko>
@@ -1003,6 +1037,7 @@ class Flicking extends Component<FlickingEvents> {
10031037
autoResize = true,
10041038
useResizeObserver = true,
10051039
resizeDebounce = 0,
1040+
resizePanelObserve = false,
10061041
maxResizeDebounce = 100,
10071042
useFractionalSize = false,
10081043
externalRenderer = null,
@@ -1049,6 +1084,7 @@ class Flicking extends Component<FlickingEvents> {
10491084
this._useResizeObserver = useResizeObserver;
10501085
this._resizeDebounce = resizeDebounce;
10511086
this._maxResizeDebounce = maxResizeDebounce;
1087+
this._resizePanelObserve = resizePanelObserve;
10521088
this._useFractionalSize = useFractionalSize;
10531089
this._externalRenderer = externalRenderer;
10541090
this._renderExternal = renderExternal;

src/core/AutoResizer.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
*/
55
import Flicking from "../Flicking";
66

7+
/**
8+
* A component that detects size change and trigger resize method when the autoResize option is used
9+
* @ko autoResize 옵션을 사용할 때 크기 변화를 감지하고 Flicking의 resize를 호출하는 컴포넌트
10+
*/
711
class AutoResizer {
812
private _flicking: Flicking;
913
private _enabled: boolean;
@@ -36,9 +40,13 @@ class AutoResizer {
3640
? new ResizeObserver(this._skipFirstResize)
3741
: new ResizeObserver(this._onResize);
3842

39-
resizeObserver.observe(flicking.viewport.element);
40-
4143
this._resizeObserver = resizeObserver;
44+
45+
this.observe(flicking.viewport.element);
46+
47+
if (flicking.resizePanelObserve) {
48+
this.observePanels();
49+
}
4250
} else {
4351
window.addEventListener("resize", this._onResize);
4452
}
@@ -48,6 +56,44 @@ class AutoResizer {
4856
return this;
4957
}
5058

59+
public observePanels(): this {
60+
this._flicking.panels.forEach(panel => {
61+
this.observe(panel.element);
62+
});
63+
return this;
64+
}
65+
66+
public unobservePanels(): this {
67+
this._flicking.panels.forEach(panel => {
68+
this.unobserve(panel.element);
69+
});
70+
return this;
71+
}
72+
73+
public observe(element: HTMLElement): this {
74+
const resizeObserver = this._resizeObserver;
75+
76+
if (!resizeObserver) return this;
77+
78+
resizeObserver.observe(element);
79+
80+
return this;
81+
}
82+
83+
public unobserve(element: HTMLElement): this {
84+
const resizeObserver = this._resizeObserver;
85+
86+
if (!resizeObserver) return this;
87+
88+
resizeObserver.unobserve(element);
89+
90+
if (this._flicking.resizePanelObserve) {
91+
this.unobservePanels();
92+
}
93+
94+
return this;
95+
}
96+
5197
public disable(): this {
5298
if (!this._enabled) return this;
5399

src/renderer/Renderer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,19 @@ abstract class Renderer {
335335
// Update camera & control
336336
this._updateCameraAndControl();
337337

338+
if (flicking.autoResize && flicking.useResizeObserver) {
339+
panelsAdded.forEach((panel) => {
340+
if (panel.element) {
341+
flicking.autoResizer.observe(panel.element);
342+
}
343+
});
344+
panelsRemoved.forEach((panel) => {
345+
if (panel.element) {
346+
flicking.autoResizer.unobserve(panel.element);
347+
}
348+
});
349+
}
350+
338351
void this.render();
339352

340353
if (!flicking.animating) {

src/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,12 @@ export const findIndex = <T>(array: T[], checker: (val: T) => boolean): number =
256256
export const getProgress = (pos: number, prev: number, next: number) => (pos - prev) / (next - prev);
257257

258258
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
259-
export const getStyle = (el: HTMLElement): CSSStyleDeclaration => window.getComputedStyle(el) || (el as any).currentStyle as CSSStyleDeclaration;
259+
export const getStyle = (el: HTMLElement): CSSStyleDeclaration => {
260+
if (!el) {
261+
return {} as CSSStyleDeclaration;
262+
}
263+
return window.getComputedStyle(el) || (el as any).currentStyle as CSSStyleDeclaration;
264+
};
260265

261266
export const setSize = (el: HTMLElement, { width, height }: Partial<{
262267
width: number | string;

test/unit/Flicking.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,65 @@ describe("Flicking", () => {
958958
});
959959
});
960960

961+
describe("resizePanelObserve", () => {
962+
it(`should call resize when size of panel is changed`, async () => {
963+
const flicking = await createFlicking(
964+
El.viewport("1000px", "1000px").add(
965+
El.camera("1000px", "1000px").add(
966+
El.panel("800px", "1000px"),
967+
El.panel("800px", "1000px"),
968+
El.panel("800px", "1000px"),
969+
)
970+
),
971+
{ autoResize: true, useResizeObserver: true, resizePanelObserve: true }
972+
);
973+
const afterResizeSpy = sinon.spy();
974+
const beforeResizeSpy = sinon.spy();
975+
976+
// wait for initial resize
977+
await waitTime(100);
978+
979+
flicking.on(EVENTS.AFTER_RESIZE, afterResizeSpy);
980+
flicking.on(EVENTS.BEFORE_RESIZE, beforeResizeSpy);
981+
flicking.panels[2].element.style.height = "3000px";
982+
983+
await waitEvent(flicking, EVENTS.AFTER_RESIZE);
984+
985+
expect(afterResizeSpy.calledOnce).to.be.true;
986+
expect(beforeResizeSpy.calledOnce).to.be.true;
987+
});
988+
989+
it("should observe size of panel element if panel element is added later", async () => {
990+
const flicking = await createFlicking(
991+
El.viewport("1000px", "1000px").add(
992+
El.camera("1000px", "1000px").add(
993+
El.panel("800px", "1000px"),
994+
El.panel("800px", "1000px"),
995+
El.panel("800px", "1000px"),
996+
)
997+
),
998+
{ autoResize: true, useResizeObserver: true, resizePanelObserve: true }
999+
);
1000+
const afterResizeSpy = sinon.spy();
1001+
const beforeResizeSpy = sinon.spy();
1002+
1003+
// wait for initial resize
1004+
await waitTime(100);
1005+
1006+
flicking.on(EVENTS.AFTER_RESIZE, afterResizeSpy);
1007+
flicking.on(EVENTS.BEFORE_RESIZE, beforeResizeSpy);
1008+
1009+
flicking.append(flicking.panels[0].element.outerHTML);
1010+
1011+
flicking.panels[3].element.style.height = "3000px";
1012+
1013+
await waitEvent(flicking, EVENTS.AFTER_RESIZE);
1014+
1015+
expect(afterResizeSpy.calledOnce).to.be.true;
1016+
expect(beforeResizeSpy.calledOnce).to.be.true;
1017+
});
1018+
});
1019+
9611020
describe("preventClickOnDrag", () => {
9621021
it("should trigger click event when Flicking's not dragged at all", async () => {
9631022
const flicking = await createFlicking(El.DEFAULT_HORIZONTAL, { preventClickOnDrag: true });

test/unit/renderer/Renderer.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ describe("Renderer", () => {
423423

424424
it("should resize the panel with image when it's loaded", async () => {
425425
const flicking = await createFlicking(El.viewport("200px", "200px").add(
426-
El.camera().add(
426+
El.camera("100%", "100%").add(
427427
El.imgPanel("100%", "100%")
428428
)
429429
), { resizeOnContentsReady: true });
@@ -438,7 +438,7 @@ describe("Renderer", () => {
438438

439439
it("should update the camera range after the image's loaded", async () => {
440440
const flicking = await createFlicking(El.viewport("200px", "200px").add(
441-
El.camera().add(
441+
El.camera("100%", "100%").add(
442442
El.imgPanel("100%", "100%")
443443
)
444444
), { resizeOnContentsReady: true });

0 commit comments

Comments
 (0)