Skip to content

Commit

Permalink
Core | Container: Improve ResizeObserver handling
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
rokotyan authored and lee00678 committed Oct 21, 2024
1 parent bcd00da commit 2603003
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 16 deletions.
4 changes: 2 additions & 2 deletions packages/ts/src/containers/single-container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ export class SingleContainer<Data> 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)
})
Expand Down
36 changes: 22 additions & 14 deletions packages/ts/src/core/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SVGDefsElement, unknown, null, undefined>
protected _svgDefsExternal: Selection<SVGDefsElement, unknown, null, undefined>
private _containerSize: { width: number; height: number }
Expand All @@ -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
Expand Down Expand Up @@ -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)
})
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 2603003

Please sign in to comment.