leptos_use_intersection_observer/
use_intersection_observer.rs1use crate::{
2 IntoElementMaybeSignal, IntoElementsMaybeSignal, UseIntersectionObserverOptions,
3 UseIntersectionObserverReturn, sendwrap_fn,
4};
5use cfg_if::cfg_if;
6use leptos::prelude::*;
7
8cfg_if! { if #[cfg(not(feature = "ssr"))] {
9 use crate::{watch_with_options, WatchOptions};
10 use std::sync::{Arc, Mutex};
13 use wasm_bindgen::prelude::*;
14}}
15
16pub fn use_intersection_observer<Els, M, F, RootM>(
66 target: Els,
67 callback: F,
68) -> UseIntersectionObserverReturn<
69 impl Fn() + Clone + Send + Sync,
70 impl Fn() + Clone + Send + Sync,
71 impl Fn() + Clone + Send + Sync,
72>
73where
74 Els: IntoElementsMaybeSignal<web_sys::Element, M>,
75 F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static,
76 web_sys::Element: IntoElementMaybeSignal<web_sys::Element, RootM>,
77{
78 use_intersection_observer_with_options::<Els, M, web_sys::Element, RootM, F>(
79 target,
80 callback,
81 UseIntersectionObserverOptions::default(),
82 )
83}
84
85#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
87pub fn use_intersection_observer_with_options<Els, M, RootEl, RootM, F>(
88 target: Els,
89 mut callback: F,
90 options: UseIntersectionObserverOptions<RootEl, RootM>,
91) -> UseIntersectionObserverReturn<
92 impl Fn() + Clone + Send + Sync,
93 impl Fn() + Clone + Send + Sync,
94 impl Fn() + Clone + Send + Sync,
95>
96where
97 Els: IntoElementsMaybeSignal<web_sys::Element, M>,
98 RootEl: IntoElementMaybeSignal<web_sys::Element, RootM>,
99 F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static,
100{
101 let UseIntersectionObserverOptions {
102 immediate,
103 root,
104 root_margin,
105 thresholds,
106 ..
107 } = options;
108
109 let (is_active, set_active) = signal(immediate);
110
111 let pause;
112 let cleanup;
113 let stop;
114
115 #[cfg(feature = "ssr")]
116 {
117 pause = || {};
118 cleanup = || {};
119 stop = || {};
120 }
121
122 #[cfg(not(feature = "ssr"))]
123 {
124 use send_wrapper::SendWrapper;
125
126 let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::IntersectionObserver)>::new(
127 move |entries: js_sys::Array, observer| {
128 #[cfg(debug_assertions)]
129 let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
130
131 callback(
132 entries
133 .to_vec()
134 .into_iter()
135 .map(|v| v.unchecked_into::<web_sys::IntersectionObserverEntry>())
136 .collect(),
137 observer,
138 );
139 },
140 )
141 .into_js_value();
142
143 let observer: Arc<Mutex<Option<SendWrapper<web_sys::IntersectionObserver>>>> =
144 Arc::new(Mutex::new(None));
145
146 cleanup = {
147 let observer = Arc::clone(&observer);
148
149 move || {
150 if let Some(o) = observer.lock().unwrap().take() {
151 o.disconnect();
152 }
153 }
154 };
155
156 let targets = target.into_elements_maybe_signal();
157 let root = root.map(|root| root.into_element_maybe_signal());
158
159 let stop_watch = {
160 let cleanup = cleanup.clone();
161
162 watch_with_options(
163 move || {
164 (
165 targets.get(),
166 root.as_ref().map(|root| root.get()),
167 is_active.get(),
168 )
169 },
170 move |values, _, _| {
171 let (targets, root, is_active) = values;
172
173 cleanup();
174
175 if !is_active {
176 return;
177 }
178
179 let options = web_sys::IntersectionObserverInit::new();
180 options.set_root_margin(&root_margin);
181 options.set_threshold(
182 &thresholds
183 .iter()
184 .copied()
185 .map(JsValue::from)
186 .collect::<js_sys::Array>(),
187 );
188
189 if let Some(Some(root)) = root {
190 let root = root.clone();
191 options.set_root(Some(&root));
192 }
193
194 let obs = web_sys::IntersectionObserver::new_with_options(
195 closure_js.clone().as_ref().unchecked_ref(),
196 &options,
197 )
198 .expect("failed to create IntersectionObserver");
199
200 for target in targets.iter().flatten() {
201 let target = target.clone();
202 obs.observe(&target);
203 }
204
205 *observer.lock().unwrap() = Some(SendWrapper::new(obs));
206 },
207 WatchOptions::default().immediate(immediate),
208 )
209 };
210
211 stop = {
212 let cleanup = cleanup.clone();
213
214 sendwrap_fn!(move || {
215 cleanup();
216 stop_watch();
217 })
218 };
219
220 on_cleanup(stop.clone());
221
222 pause = {
223 let cleanup = cleanup.clone();
224
225 sendwrap_fn!(move || {
226 cleanup();
227 set_active.set(false);
228 })
229 };
230 }
231
232 UseIntersectionObserverReturn {
233 is_active: is_active.into(),
234 pause,
235 resume: sendwrap_fn!(move || {
236 cleanup();
237 set_active.set(true);
238 }),
239 stop,
240 }
241}