Refactored element observables, removed memleaks

This commit is contained in:
squidfunk 2020-08-22 17:58:54 +02:00
parent 989b8597c5
commit 8a6b89ffe1
3 changed files with 73 additions and 17 deletions

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, fromEvent, merge } from "rxjs" import { Observable, fromEvent, merge } from "rxjs"
import { map, shareReplay, startWith } from "rxjs/operators" import { map, startWith } from "rxjs/operators"
import { getActiveElement } from "../_" import { getActiveElement } from "../_"
@ -62,7 +62,6 @@ export function watchElementFocus(
) )
.pipe( .pipe(
map(({ type }) => type === "focus"), map(({ type }) => type === "focus"),
startWith(el === getActiveElement()), startWith(el === getActiveElement())
shareReplay(1)
) )
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, fromEvent, merge } from "rxjs" import { Observable, fromEvent, merge } from "rxjs"
import { map, shareReplay, startWith } from "rxjs/operators" import { map, startWith } from "rxjs/operators"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -71,7 +71,6 @@ export function watchElementOffset(
) )
.pipe( .pipe(
map(() => getElementOffset(el)), map(() => getElementOffset(el)),
startWith(getElementOffset(el)), startWith(getElementOffset(el))
shareReplay(1)
) )
} }

View File

@ -21,8 +21,23 @@
*/ */
import ResizeObserver from "resize-observer-polyfill" import ResizeObserver from "resize-observer-polyfill"
import { Observable, fromEventPattern } from "rxjs" import {
import { shareReplay, startWith } from "rxjs/operators" NEVER,
Observable,
Subject,
defer,
merge,
of
} from "rxjs"
import {
filter,
finalize,
map,
shareReplay,
startWith,
switchMap,
tap
} from "rxjs/operators"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -36,6 +51,40 @@ export interface ElementSize {
height: number /* Element height */ height: number /* Element height */
} }
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Resize observer entry subject
*/
const entry$ = new Subject<ResizeObserverEntry>()
/**
* Resize observer observable
*
* This observable will create a `ResizeObserver` on the first subscription
* and will automatically terminate it when there are no more subscribers.
* It's quite important to centralize observation in a single `ResizeObserver`,
* as the performance difference can be quite dramatic, as the link shows.
*
* @see https://bit.ly/3iIYfEm - Google Groups on performance
*/
const observer$ = defer(() => of(
new ResizeObserver(entries => {
for (const entry of entries)
entry$.next(entry)
})
))
.pipe(
switchMap(resize => merge(of(resize), NEVER)
.pipe(
finalize(() => resize.disconnect())
)
),
shareReplay({ bufferSize: 1, refCount: true })
)
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -59,6 +108,11 @@ export function getElementSize(el: HTMLElement): ElementSize {
/** /**
* Watch element size * Watch element size
* *
* This function returns an observable that will subscribe to a single internal
* instance of `ResizeObserver` upon subscription, and emit resize events until
* termination. Note that this function should not be called with the same
* element twice, as the first unsubscription will terminate observation.
*
* @param el - Element * @param el - Element
* *
* @return Element size observable * @return Element size observable
@ -66,15 +120,19 @@ export function getElementSize(el: HTMLElement): ElementSize {
export function watchElementSize( export function watchElementSize(
el: HTMLElement el: HTMLElement
): Observable<ElementSize> { ): Observable<ElementSize> {
return fromEventPattern<ElementSize>(next => { return observer$
new ResizeObserver(([{ contentRect }]) => next({
width: Math.round(contentRect.width),
height: Math.round(contentRect.height)
}))
.observe(el)
})
.pipe( .pipe(
startWith(getElementSize(el)), tap(observer => observer.observe(el)),
shareReplay(1) switchMap(observer => entry$
.pipe(
filter(({ target }) => target === el),
finalize(() => observer.unobserve(el)),
map(({ contentRect }) => ({
width: contentRect.width,
height: contentRect.height
}))
)
),
startWith(getElementSize(el))
) )
} }