From 26030031fae8b4c1a955fa8b57037d3fa9963329 Mon Sep 17 00:00:00 2001 From: Nikita Rokotyan Date: Fri, 11 Oct 2024 15:14:27 -0700 Subject: [PATCH] Core | Container: Improve ResizeObserver handling - Use requestAnimationFrame to avoid multiple resize events when scrollbars appear/disappear - Cancel previous animation frame IDs before scheduling new ones - Renamed `_requestedAnimationFrame` to `_renderAnimationFrameId` Fixes #455 --- .../src/containers/single-container/index.ts | 4 +-- packages/ts/src/core/container/index.ts | 36 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/ts/src/containers/single-container/index.ts b/packages/ts/src/containers/single-container/index.ts index 11aba2ba7..3752f9a9a 100644 --- a/packages/ts/src/containers/single-container/index.ts +++ b/packages/ts/src/containers/single-container/index.ts @@ -147,8 +147,8 @@ export class SingleContainer extends ContainerCore { if (!this._resizeObserver) this._setUpResizeObserver() // Schedule the actual rendering in the next frame - cancelAnimationFrame(this._requestedAnimationFrame) - this._requestedAnimationFrame = requestAnimationFrame(() => { + cancelAnimationFrame(this._renderAnimationFrameId) + this._renderAnimationFrameId = requestAnimationFrame(() => { this._preRender() this._render(duration) }) diff --git a/packages/ts/src/core/container/index.ts b/packages/ts/src/core/container/index.ts index 7c4c78346..9e828cc0c 100644 --- a/packages/ts/src/core/container/index.ts +++ b/packages/ts/src/core/container/index.ts @@ -18,9 +18,10 @@ export class ContainerCore { protected _defaultConfig: ContainerConfigInterface = ContainerDefaultConfig protected _container: HTMLElement - protected _requestedAnimationFrame: number + protected _renderAnimationFrameId: number protected _isFirstRender = true protected _resizeObserver: ResizeObserver | undefined + protected _resizeObserverAnimationFrameId: number protected _svgDefs: Selection protected _svgDefsExternal: Selection private _containerSize: { width: number; height: number } @@ -29,7 +30,7 @@ export class ContainerCore { static DEFAULT_CONTAINER_HEIGHT = 300 constructor (element: HTMLElement) { - this._requestedAnimationFrame = null + this._renderAnimationFrameId = null this._container = element // Setting `role` attribute to `image` to make the container accessible @@ -96,8 +97,8 @@ export class ContainerCore { if (!this._resizeObserver) this._setUpResizeObserver() // Schedule the actual rendering in the next frame - cancelAnimationFrame(this._requestedAnimationFrame) - this._requestedAnimationFrame = requestAnimationFrame(() => { + cancelAnimationFrame(this._renderAnimationFrameId) + this._renderAnimationFrameId = requestAnimationFrame(() => { this._preRender() this._render(duration) }) @@ -137,25 +138,32 @@ export class ContainerCore { protected _setUpResizeObserver (): void { if (this._resizeObserver) return + const containerRect = this._container.getBoundingClientRect() this._containerSize = { width: containerRect.width, height: containerRect.height } this._resizeObserver = new ResizeObserver((entries, observer) => { - const resizedContainerRect = this._container.getBoundingClientRect() - const resizedContainerSize = { width: resizedContainerRect.width, height: resizedContainerRect.height } - const hasSizeChanged = !isEqual(this._containerSize, resizedContainerSize) - // Do resize only if element is attached to the DOM - // will come in useful when some ancestor of container becomes detached - if (hasSizeChanged && resizedContainerSize.width && resizedContainerSize.height) { - this._containerSize = resizedContainerSize - this._onResize() - } + // Using request animation frame to avoid multiple resize events when scrollbars appear/disappear + // See more: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors + cancelAnimationFrame(this._resizeObserverAnimationFrameId) + this._resizeObserverAnimationFrameId = requestAnimationFrame(() => { + const resizedContainerRect = this._container.getBoundingClientRect() + const resizedContainerSize = { width: resizedContainerRect.width, height: resizedContainerRect.height } + const hasSizeChanged = !isEqual(this._containerSize, resizedContainerSize) + // Do resize only if element is attached to the DOM + // will come in useful when some ancestor of container becomes detached + if (hasSizeChanged && resizedContainerSize.width && resizedContainerSize.height) { + this._containerSize = resizedContainerSize + this._onResize() + } + }) }) this._resizeObserver.observe(this._container) } public destroy (): void { - cancelAnimationFrame(this._requestedAnimationFrame) + cancelAnimationFrame(this._renderAnimationFrameId) + cancelAnimationFrame(this._resizeObserverAnimationFrameId) this._resizeObserver?.disconnect() this.svg.remove() }