21
21
*/
22
22
23
23
import ResizeObserver from "resize-observer-polyfill"
24
- import { Observable , fromEventPattern } from "rxjs"
25
- import { shareReplay , startWith } from "rxjs/operators"
24
+ import {
25
+ NEVER ,
26
+ Observable ,
27
+ Subject ,
28
+ defer ,
29
+ merge ,
30
+ of
31
+ } from "rxjs"
32
+ import {
33
+ filter ,
34
+ finalize ,
35
+ map ,
36
+ shareReplay ,
37
+ startWith ,
38
+ switchMap ,
39
+ tap
40
+ } from "rxjs/operators"
26
41
27
42
/* ----------------------------------------------------------------------------
28
43
* Types
@@ -36,6 +51,40 @@ export interface ElementSize {
36
51
height : number /* Element height */
37
52
}
38
53
54
+ /* ----------------------------------------------------------------------------
55
+ * Data
56
+ * ------------------------------------------------------------------------- */
57
+
58
+ /**
59
+ * Resize observer entry subject
60
+ */
61
+ const entry$ = new Subject < ResizeObserverEntry > ( )
62
+
63
+ /**
64
+ * Resize observer observable
65
+ *
66
+ * This observable will create a `ResizeObserver` on the first subscription
67
+ * and will automatically terminate it when there are no more subscribers.
68
+ * It's quite important to centralize observation in a single `ResizeObserver`,
69
+ * as the performance difference can be quite dramatic, as the link shows.
70
+ *
71
+ * @see https://bit.ly/3iIYfEm - Google Groups on performance
72
+ */
73
+ const observer$ = defer ( ( ) => of (
74
+ new ResizeObserver ( entries => {
75
+ for ( const entry of entries )
76
+ entry$ . next ( entry )
77
+ } )
78
+ ) )
79
+ . pipe (
80
+ switchMap ( resize => merge ( of ( resize ) , NEVER )
81
+ . pipe (
82
+ finalize ( ( ) => resize . disconnect ( ) )
83
+ )
84
+ ) ,
85
+ shareReplay ( { bufferSize : 1 , refCount : true } )
86
+ )
87
+
39
88
/* ----------------------------------------------------------------------------
40
89
* Functions
41
90
* ------------------------------------------------------------------------- */
@@ -59,22 +108,31 @@ export function getElementSize(el: HTMLElement): ElementSize {
59
108
/**
60
109
* Watch element size
61
110
*
111
+ * This function returns an observable that will subscribe to a single internal
112
+ * instance of `ResizeObserver` upon subscription, and emit resize events until
113
+ * termination. Note that this function should not be called with the same
114
+ * element twice, as the first unsubscription will terminate observation.
115
+ *
62
116
* @param el - Element
63
117
*
64
118
* @return Element size observable
65
119
*/
66
120
export function watchElementSize (
67
121
el : HTMLElement
68
122
) : Observable < ElementSize > {
69
- return fromEventPattern < ElementSize > ( next => {
70
- new ResizeObserver ( ( [ { contentRect } ] ) => next ( {
71
- width : Math . round ( contentRect . width ) ,
72
- height : Math . round ( contentRect . height )
73
- } ) )
74
- . observe ( el )
75
- } )
123
+ return observer$
76
124
. pipe (
77
- startWith ( getElementSize ( el ) ) ,
78
- shareReplay ( 1 )
125
+ tap ( observer => observer . observe ( el ) ) ,
126
+ switchMap ( observer => entry$
127
+ . pipe (
128
+ filter ( ( { target } ) => target === el ) ,
129
+ finalize ( ( ) => observer . unobserve ( el ) ) ,
130
+ map ( ( { contentRect } ) => ( {
131
+ width : contentRect . width ,
132
+ height : contentRect . height
133
+ } ) )
134
+ )
135
+ ) ,
136
+ startWith ( getElementSize ( el ) )
79
137
)
80
138
}
0 commit comments