leptos_use/use_element_bounding.rs
1use crate::core::IntoElementMaybeSignal;
2use default_struct_builder::DefaultBuilder;
3use leptos::prelude::*;
4use leptos::reactive::wrappers::read::Signal;
5
6/// Reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element
7///
8/// ## Demo
9///
10/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_element_bounding)
11///
12/// ## Usage
13///
14/// ```
15/// # use leptos::prelude::*;
16/// # use leptos::html::Div;
17/// # use leptos_use::{use_element_bounding, UseElementBoundingReturn};
18/// #
19/// # #[component]
20/// # fn Demo() -> impl IntoView {
21/// let el = NodeRef::<Div>::new();
22/// let UseElementBoundingReturn {
23/// x, y, top,right,bottom,left, width, height, ..
24/// } = use_element_bounding(el);
25///
26/// view! { <div node_ref=el></div> }
27/// # }
28/// ```
29///
30/// ## SendWrapped Return
31///
32/// The returned closure `update` is a sendwrapped function. It can
33/// only be called from the same thread that called `use_element_bounding`.
34///
35/// ## Server-Side Rendering
36///
37/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
38///
39/// On the server the returned signals always are `0.0` and `update` is a no-op.
40pub fn use_element_bounding<El, M>(
41 target: El,
42) -> UseElementBoundingReturn<impl Fn() + Clone + Send + Sync>
43where
44 El: IntoElementMaybeSignal<web_sys::Element, M>,
45{
46 use_element_bounding_with_options(target, UseElementBoundingOptions::default())
47}
48
49/// Version of [`use_element_bounding`] that takes a `UseElementBoundingOptions`. See [`use_element_bounding`] for how to use.
50pub fn use_element_bounding_with_options<El, M>(
51 target: El,
52 options: UseElementBoundingOptions,
53) -> UseElementBoundingReturn<impl Fn() + Clone + Send + Sync>
54where
55 El: IntoElementMaybeSignal<web_sys::Element, M>,
56{
57 let (height, set_height) = signal(0.0);
58 let (width, set_width) = signal(0.0);
59 let (left, set_left) = signal(0.0);
60 let (right, set_right) = signal(0.0);
61 let (top, set_top) = signal(0.0);
62 let (bottom, set_bottom) = signal(0.0);
63 let (x, set_x) = signal(0.0);
64 let (y, set_y) = signal(0.0);
65
66 let update;
67
68 #[cfg(feature = "ssr")]
69 {
70 let _ = target;
71 let _ = options;
72
73 let _ = set_height;
74 let _ = set_width;
75 let _ = set_left;
76 let _ = set_right;
77 let _ = set_top;
78 let _ = set_bottom;
79 let _ = set_x;
80 let _ = set_y;
81
82 update = move || ();
83 }
84
85 #[cfg(not(feature = "ssr"))]
86 {
87 use crate::{
88 UseEventListenerOptions, sendwrap_fn, use_event_listener_with_options,
89 use_resize_observer, use_window,
90 };
91 use leptos::ev::{resize, scroll};
92
93 let UseElementBoundingOptions {
94 reset,
95 window_resize,
96 window_scroll,
97 immediate,
98 } = options;
99
100 let target = target.into_element_maybe_signal();
101
102 update = sendwrap_fn!(move || {
103 let el = target.get_untracked();
104
105 if let Some(el) = el {
106 let rect = el.get_bounding_client_rect();
107
108 set_height.set(rect.height());
109 set_width.set(rect.width());
110 set_left.set(rect.x());
111 set_right.set(rect.x() + rect.width());
112 set_top.set(rect.y());
113 set_bottom.set(rect.y() + rect.height());
114 set_x.set(rect.x());
115 set_y.set(rect.y());
116 } else if reset {
117 set_height.set(0.0);
118 set_width.set(0.0);
119 set_left.set(0.0);
120 set_right.set(0.0);
121 set_top.set(0.0);
122 set_bottom.set(0.0);
123 set_x.set(0.0);
124 set_y.set(0.0);
125 }
126 });
127
128 use_resize_observer(target, {
129 let update = update.clone();
130
131 move |_, _| {
132 update();
133 }
134 });
135
136 Effect::watch(
137 move || target.get(),
138 {
139 let update = update.clone();
140 move |_, _, _| {
141 update();
142 }
143 },
144 false,
145 );
146
147 if window_scroll {
148 let _ = use_event_listener_with_options(
149 use_window(),
150 scroll,
151 {
152 let update = update.clone();
153 move |_| update()
154 },
155 UseEventListenerOptions::default()
156 .capture(true)
157 .passive(true),
158 );
159 }
160
161 if window_resize {
162 let _ = use_event_listener_with_options(
163 use_window(),
164 resize,
165 {
166 let update = update.clone();
167 move |_| update()
168 },
169 UseEventListenerOptions::default().passive(true),
170 );
171 }
172
173 if immediate {
174 update();
175 }
176 }
177
178 UseElementBoundingReturn {
179 height: height.into(),
180 width: width.into(),
181 left: left.into(),
182 right: right.into(),
183 top: top.into(),
184 bottom: bottom.into(),
185 x: x.into(),
186 y: y.into(),
187 update,
188 }
189}
190
191/// Options for [`use_element_bounding_with_options`].
192#[derive(DefaultBuilder)]
193pub struct UseElementBoundingOptions {
194 /// Reset values to 0 on component disposal
195 ///
196 /// Default: `true`
197 pub reset: bool,
198
199 /// Listen to window resize event
200 ///
201 /// Default: `true`
202 pub window_resize: bool,
203
204 /// Listen to window scroll event
205 ///
206 /// Default: `true`
207 pub window_scroll: bool,
208
209 /// Immediately call update
210 ///
211 /// Default: `true`
212 pub immediate: bool,
213}
214
215impl Default for UseElementBoundingOptions {
216 fn default() -> Self {
217 Self {
218 reset: true,
219 window_resize: true,
220 window_scroll: true,
221 immediate: true,
222 }
223 }
224}
225
226/// Return type of [`use_element_bounding`].
227pub struct UseElementBoundingReturn<F>
228where
229 F: Fn() + Clone + Send + Sync,
230{
231 /// Reactive version of [`BoudingClientRect.height`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/height)
232 pub height: Signal<f64>,
233 /// Reactive version of [`BoudingClientRect.width`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/width)
234 pub width: Signal<f64>,
235 /// Reactive version of [`BoudingClientRect.left`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/left)
236 pub left: Signal<f64>,
237 /// Reactive version of [`BoudingClientRect.right`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/right)
238 pub right: Signal<f64>,
239 /// Reactive version of [`BoudingClientRect.top`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/top)
240 pub top: Signal<f64>,
241 /// Reactive version of [`BoudingClientRect.bottom`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/bottom)
242 pub bottom: Signal<f64>,
243 /// Reactive version of [`BoudingClientRect.x`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/x)
244 pub x: Signal<f64>,
245 /// Reactive version of [`BoudingClientRect.y`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/y)
246 pub y: Signal<f64>,
247 /// Function to re-evaluate `get_bounding_client_rect()` and update the signals.
248 pub update: F,
249}