Skip to content

Commit

Permalink
feat: add useResizeObserver option (#55)
Browse files Browse the repository at this point in the history
* feat: add useResizeObserver option

* test: test useResizeObserver

* fix: fix typescript version

* skip: apply review
  • Loading branch information
daybrush authored Feb 17, 2022
1 parent e7c5db1 commit dc90ede
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 65 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
"storybook-dark-mode": "^1.0.3",
"ts-node": "^9.1.1",
"tslib": "^2.0.3",
"typescript": "^3.9.7"
"typescript": "^4.2.4"
},
"dependencies": {
"@egjs/children-differ": "^1.0.1",
Expand Down
71 changes: 29 additions & 42 deletions src/ContainerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,27 @@
* MIT license
*/
import Component from "@egjs/component";
import { DestroyOptions } from ".";
import { DestroyOptions, RECT_NAMES, ResizeWatcher, SizeRect } from ".";
import { DEFAULT_GRID_OPTIONS } from "./consts";
import { DOMRect } from "./types";

export interface ContainerManagerOptions {
horizontal?: boolean;
autoResize?: boolean;
resizeDebounce?: number;
maxResizeDebounce?: number;
useResizeObserver?: boolean;
}

export interface ContainerManagerStatus {
rect: DOMRect;
rect: SizeRect;
}
export interface ContainerManagerEvents {
resize: void;
}
export class ContainerManager extends Component<ContainerManagerEvents> {
protected options: Required<ContainerManagerOptions>;
protected rect: DOMRect;
protected orgCSSText: string;
private _resizeTimer = 0;
private _maxResizeDebounceTimer = 0;
private _watcher: ResizeWatcher;

constructor(protected container: HTMLElement, options: ContainerManagerOptions) {
super();
Expand All @@ -35,6 +33,7 @@ export class ContainerManager extends Component<ContainerManagerEvents> {
autoResize: DEFAULT_GRID_OPTIONS.autoResize,
resizeDebounce: DEFAULT_GRID_OPTIONS.resizeDebounce,
maxResizeDebounce: DEFAULT_GRID_OPTIONS.maxResizeDebounce,
useResizeObserver: DEFAULT_GRID_OPTIONS.useResizeObserver,
...options,
};

Expand All @@ -49,34 +48,35 @@ export class ContainerManager extends Component<ContainerManagerEvents> {
});
}
public getRect() {
return this.rect;
return this._watcher.getRect();
}
public setRect(rect: DOMRect) {
this.rect = { ...rect };
public setRect(rect: SizeRect) {
this._watcher.setRect(rect);
}
public getInlineSize() {
return this.rect[this.options.horizontal ? "height" : "width"];
return this.getRect()[this._names.inlineSize];
}
public getContentSize() {
return this.rect[this.options.horizontal ? "width" : "height"]!;
return this.getRect()[this._names.contentSize];
}
public getStatus() {
return {
rect: { ...this.rect },
};
return { rect: this._watcher.getRect() };
}
public setStatus(status: ContainerManagerStatus) {
this.rect = { ...status.rect };

this.setRect(status.rect);
this.setContentSize(this.getContentSize());
}
public setContentSize(size: number) {
const sizeName = this.options.horizontal ? "width" : "height";
this.rect[sizeName] = size;
this.setRect({
...this.getRect(),
[sizeName]: size,
});
this.container.style[sizeName] = `${size}px`;
}
public destroy(options: DestroyOptions = {}) {
window.removeEventListener("resize", this._scheduleResize);
this._watcher.destroy();

if (!options.preserveUI) {
this.container.style.cssText = this.orgCSSText;
}
Expand All @@ -90,33 +90,20 @@ export class ContainerManager extends Component<ContainerManagerEvents> {
if (style.position === "static") {
container.style.position = "relative";
}
if (this.options.autoResize) {
window.addEventListener("resize", this._scheduleResize);
}
const options = this.options;

this._watcher = new ResizeWatcher(container, {
useWindowResize: options.autoResize,
useResizeObserver: options.useResizeObserver,
resizeDebounce: options.resizeDebounce,
maxResizeDebounce: options.maxResizeDebounce,
watchDirection: options.useResizeObserver ? this._names.inlineSize : false,
}).listen(this._onResize);
}
private _onResize = () => {
clearTimeout(this._resizeTimer);
clearTimeout(this._maxResizeDebounceTimer);

this._maxResizeDebounceTimer = 0;
this._resizeTimer = 0;

this.trigger("resize");
}
private _scheduleResize = () => {
const {
resizeDebounce,
maxResizeDebounce,
} = this.options;


if (!this._maxResizeDebounceTimer && maxResizeDebounce >= resizeDebounce) {
this._maxResizeDebounceTimer = window.setTimeout(this._onResize, maxResizeDebounce);
}
if (this._resizeTimer) {
clearTimeout(this._resizeTimer);
this._resizeTimer = 0;
}
this._resizeTimer = window.setTimeout(this._onResize, resizeDebounce);
private get _names() {
return RECT_NAMES[this.options.horizontal ? "horizontal" : "vertical"];
}
}
42 changes: 27 additions & 15 deletions src/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ abstract class Grid<Options extends GridOptions = GridOptions> extends Component
maxResizeDebounce,
autoResize,
useRoundedSize,
useResizeObserver,
} = this.options;

// TODO: 테스트용 설정
Expand All @@ -84,6 +85,7 @@ abstract class Grid<Options extends GridOptions = GridOptions> extends Component
resizeDebounce,
maxResizeDebounce,
autoResize,
useResizeObserver,
}).on("resize", this._onResize);
this.itemRenderer = externalItemRenderer!
|| new ItemRenderer({
Expand Down Expand Up @@ -215,18 +217,7 @@ abstract class Grid<Options extends GridOptions = GridOptions> extends Component
* ```
*/
public renderItems(options: RenderOptions = {}) {
this._clearRenderTimer();

if (!this.getItems().length && this.getChildren().length) {
this.syncElements(options);
} else if (options.useResize || options.useOrgResize) {
// Resize container and Update all items
this._resizeContainer();
this.updateItems(this.items, options);
} else {
// Update only items that need to be updated.
this.checkReady(options);
}
this._renderItems(options);
return this;
}
/**
Expand Down Expand Up @@ -509,14 +500,35 @@ abstract class Grid<Options extends GridOptions = GridOptions> extends Component
this.itemRenderer.setContainerRect(this.containerManager.getRect());
}
private _onResize = () => {
this.renderItems({
this._renderItems({
useResize: true,
});
}, true);
}

private _init() {
this._resizeContainer();
}
private _renderItems(options: RenderOptions = {}, isTrusted?: boolean) {
this._clearRenderTimer();

const isResize = options.useResize || options.useOrgResize;


if (isResize && !isTrusted) {
// Resize container
// isTrusted has already been resized internally.
this._resizeContainer();
}

if (!this.getItems().length && this.getChildren().length) {
this.syncElements(options);
} else if (isResize) {
// Update all items
this.updateItems(this.items, options);
} else {
// Update only items that need to be updated.
this.checkReady(options);
}
}
}

interface Grid extends Properties<typeof Grid> { }
Expand Down
117 changes: 117 additions & 0 deletions src/ResizeWatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Component from "@egjs/component";
import { SizeRect } from "./types";
import { isString } from "./utils";

export interface ResizeWatherOptions {
resizeDebounce?: number;
maxResizeDebounce?: number;
useResizeObserver?: boolean;
useWindowResize?: boolean;
watchDirection?: "width" | "height" | "box" | false;
rectBox?: "border-box" | "content-box";
}

export class ResizeWatcher {
private _resizeTimer = 0;
private _maxResizeDebounceTimer = 0;
private _emitter: Component<{ resize: void }>;
private _observer: ResizeObserver | null;
protected container: HTMLElement;
protected rect: SizeRect = { width: 0, height: 0 };
private _options!: Required<ResizeWatherOptions>;

constructor(container: HTMLElement | string, options: ResizeWatherOptions = {}) {
this._options = {
resizeDebounce: 100,
maxResizeDebounce: 0,
useResizeObserver: false,
useWindowResize: true,
watchDirection: false,
rectBox: "content-box",
...options,
};

this.container = isString(container) ? document.querySelector<HTMLElement>(container)! : container;
this._init();
}
public getRect() {
return this.rect;
}
public setRect(rect: SizeRect) {
this.rect = { ...rect };
}
public resize() {
const container = this.container;

this.setRect(this._options.rectBox === "border-box" ? {
width: container.offsetWidth,
height: container.offsetHeight,
} : {
width: container.clientWidth,
height: container.clientHeight,
});
}
public listen(callback: () => void) {
this._emitter.on("resize", callback);
return this;
}
public destroy() {
this._observer?.disconnect();
if (this._options.useWindowResize) {
window.removeEventListener("reisze", this._onResize);
}
}
private _init() {
const container = this.container;
const options = this._options;

this._emitter = new Component();
if (options.useResizeObserver && !!window.ResizeObserver) {
this._observer = new window.ResizeObserver(this._scheduleResize);
this._observer.observe(container, {
box: options.rectBox,
});
}
if (options.useWindowResize) {
window.addEventListener("resize", this._scheduleResize);
}
this.resize();
}
private _onResize = () => {
clearTimeout(this._resizeTimer);
clearTimeout(this._maxResizeDebounceTimer);

this._maxResizeDebounceTimer = 0;
this._resizeTimer = 0;

const watchDirection = this._options.watchDirection;
const prevRect = this.rect;
this.resize();
const rect = this.rect;
const isWatchWidth = watchDirection === "box" || watchDirection === "width";
const isWatchHeight = watchDirection === "box" || watchDirection === "height";
const isResize = !watchDirection
|| (isWatchWidth && prevRect.width !== rect.width)
|| (isWatchHeight && prevRect.height !== rect.height);

if (isResize) {
this._emitter.trigger("resize");
}
}
private _scheduleResize = () => {
const {
resizeDebounce,
maxResizeDebounce,
} = this._options;


if (!this._maxResizeDebounceTimer && maxResizeDebounce >= resizeDebounce) {
this._maxResizeDebounceTimer = window.setTimeout(this._onResize, maxResizeDebounce);
}
if (this._resizeTimer) {
clearTimeout(this._resizeTimer);
this._resizeTimer = 0;
}
this._resizeTimer = window.setTimeout(this._onResize, resizeDebounce);
}
}
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const DEFAULT_GRID_OPTIONS: Required<GridOptions> = {
outlineLength: 0,
outlineSize: 0,
useRoundedSize: true,
useResizeObserver: false,
};

export enum PROPERTY_TYPE {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./types";
export * from "./Grid";
export * from "./GridItem";
export * from "./ContainerManager";
export * from "./ResizeWatcher";
export * from "./consts";
export {
GetterSetter,
Expand Down
Loading

0 comments on commit dc90ede

Please sign in to comment.