Skip to main content

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}