@@ -12,7 +12,7 @@ use web_sys::{
1212
1313use crate :: {
1414 types:: { ElementOrVirtual , OwnedElementOrVirtual } ,
15- utils:: get_bounding_client_rect:: get_bounding_client_rect,
15+ utils:: { get_bounding_client_rect:: get_bounding_client_rect, rects_are_equal :: rects_are_equal } ,
1616} ;
1717
1818fn request_animation_frame ( callback : & Closure < dyn FnMut ( ) > ) -> i32 {
@@ -63,20 +63,26 @@ fn observe_move(element: Element, on_move: Rc<dyn Fn()>) -> Box<dyn Fn()> {
6363 * refresh_closure_clone. borrow_mut ( ) = Some ( Box :: new ( move |skip : bool , threshold : f64 | {
6464 refresh_cleanup ( ) ;
6565
66- let rect = element. get_bounding_client_rect ( ) ;
66+ let element_rect_for_root_margin = element. get_bounding_client_rect ( ) ;
6767
6868 if !skip {
6969 on_move ( ) ;
7070 }
7171
72- if rect. width ( ) == 0.0 || rect. height ( ) == 0.0 {
72+ if element_rect_for_root_margin. width ( ) == 0.0
73+ || element_rect_for_root_margin. height ( ) == 0.0
74+ {
7375 return ;
7476 }
7577
76- let inset_top = rect. top ( ) . floor ( ) ;
77- let inset_right = ( root. client_width ( ) as f64 - ( rect. left ( ) + rect. width ( ) ) ) . floor ( ) ;
78- let inset_bottom = ( root. client_height ( ) as f64 - ( rect. top ( ) + rect. height ( ) ) ) . floor ( ) ;
79- let inset_left = rect. left ( ) . floor ( ) ;
78+ let inset_top = element_rect_for_root_margin. top ( ) . floor ( ) ;
79+ let inset_right = ( root. client_width ( ) as f64
80+ - ( element_rect_for_root_margin. left ( ) + element_rect_for_root_margin. width ( ) ) )
81+ . floor ( ) ;
82+ let inset_bottom = ( root. client_height ( ) as f64
83+ - ( element_rect_for_root_margin. top ( ) + element_rect_for_root_margin. height ( ) ) )
84+ . floor ( ) ;
85+ let inset_left = element_rect_for_root_margin. left ( ) . floor ( ) ;
8086 let root_margin = format ! (
8187 "{}px {}px {}px {}px" ,
8288 -inset_top, -inset_right, -inset_bottom, -inset_left
@@ -95,33 +101,60 @@ fn observe_move(element: Element, on_move: Rc<dyn Fn()>) -> Box<dyn Fn()> {
95101 let observe_timeout_id = timeout_id. clone ( ) ;
96102 let observe_window = window. clone ( ) ;
97103 let observe_refresh = refresh_closure. clone ( ) ;
98- let local_observe_closure = Closure :: new ( move |entries : Vec < IntersectionObserverEntry > | {
99- let ratio = entries[ 0 ] . intersection_ratio ( ) ;
104+ let local_observe_closure = Closure :: new ( {
105+ let element = element. clone ( ) ;
106+
107+ move |entries : Vec < IntersectionObserverEntry > | {
108+ let ratio = entries[ 0 ] . intersection_ratio ( ) ;
109+
110+ if ratio != threshold {
111+ if !* is_first_update. borrow ( ) {
112+ observe_refresh
113+ . borrow ( )
114+ . as_ref ( )
115+ . expect ( "Refresh closure should exist." ) (
116+ false , 1.0
117+ ) ;
118+ return ;
119+ }
100120
101- if ratio != threshold {
102- if !* is_first_update. borrow ( ) {
103- observe_refresh
104- . borrow ( )
105- . as_ref ( )
106- . expect ( "Refresh closure should exist." ) ( false , 1.0 ) ;
107- return ;
121+ if ratio == 0.0 {
122+ // If the reference is clipped, the ratio is 0. Throttle the refresh to prevent an infinite loop of updates.
123+ observe_timeout_id. replace ( Some (
124+ observe_window
125+ . set_timeout_with_callback_and_timeout_and_arguments_0 (
126+ ( * timeout_closure) . as_ref ( ) . unchecked_ref ( ) ,
127+ 1000 ,
128+ )
129+ . expect ( "Set timeout should be successful." ) ,
130+ ) ) ;
131+ } else {
132+ observe_refresh
133+ . borrow ( )
134+ . as_ref ( )
135+ . expect ( "Refresh closure should exist." ) (
136+ false , ratio
137+ ) ;
138+ }
108139 }
109140
110- if ratio == 0.0 {
111- // If the reference is clipped, the ratio is 0. Throttle the refresh to prevent an infinite loop of updates.
112- observe_timeout_id. replace ( Some (
113- observe_window
114- . set_timeout_with_callback_and_timeout_and_arguments_0 (
115- ( * timeout_closure) . as_ref ( ) . unchecked_ref ( ) ,
116- 1000 ,
117- )
118- . expect ( "Set timeout should be successful." ) ,
119- ) ) ;
120- } else {
141+ if ratio == 1.0
142+ && !rects_are_equal (
143+ & element_rect_for_root_margin. clone ( ) . into ( ) ,
144+ & element. get_bounding_client_rect ( ) . into ( ) ,
145+ )
146+ {
147+ // It's possible that even though the ratio is reported as 1, the
148+ // element is not actually fully within the IntersectionObserver's root
149+ // area anymore. This can happen under performance constraints. This may
150+ // be a bug in the browser's IntersectionObserver implementation. To
151+ // work around this, we compare the element's bounding rect now with
152+ // what it was at the time we created the IntersectionObserver. If they
153+ // are not equal then the element moved, so we refresh.
121154 observe_refresh
122155 . borrow ( )
123156 . as_ref ( )
124- . expect ( "Refresh closure should exist." ) ( false , ratio ) ;
157+ . expect ( "Refresh closure should exist." ) ( false , 1.0 ) ;
125158 }
126159
127160 is_first_update. replace ( false ) ;
@@ -373,11 +406,7 @@ pub fn auto_update(
373406 get_bounding_client_rect ( ( & owned_reference) . into ( ) , false , false , None ) ;
374407
375408 if let Some ( prev_ref_rect) = prev_ref_rect. borrow ( ) . as_ref ( ) {
376- if next_ref_rect. x != prev_ref_rect. x
377- || next_ref_rect. y != prev_ref_rect. y
378- || next_ref_rect. width != prev_ref_rect. width
379- || next_ref_rect. height != prev_ref_rect. height
380- {
409+ if !rects_are_equal ( prev_ref_rect, & next_ref_rect) {
381410 update ( ) ;
382411 }
383412 }
0 commit comments