yew_hooks/hooks/
use_measure.rs

1use wasm_bindgen::{prelude::*, JsCast};
2use web_sys::Element;
3use yew::prelude::*;
4
5use super::use_raf_state;
6use crate::web_sys_ext::{ResizeObserver, ResizeObserverEntry};
7
8#[derive(PartialEq, Default, Clone)]
9pub struct UseMeasureState {
10    pub x: f64,
11    pub y: f64,
12    pub width: f64,
13    pub height: f64,
14    pub top: f64,
15    pub left: f64,
16    pub bottom: f64,
17    pub right: f64,
18}
19
20/// A sensor hook that tracks an HTML element's dimensions using the `ResizeObserver` API.
21///
22/// # Example
23///
24/// ```rust
25/// # use yew::prelude::*;
26/// #
27/// use yew_hooks::prelude::*;
28///
29/// #[function_component(UseMeasure)]
30/// fn measure() -> Html {
31///     let node =  use_node_ref();
32///     let state = use_measure(node.clone());
33///     
34///     html! {
35///         <div ref={node}>
36///             <b>{ " X: " }</b>
37///             { state.x }
38///             <b>{ " Y: " }</b>
39///             { state.y }
40///             <b>{ " Width: " }</b>
41///             { state.width }
42///             <b>{ " Height: " }</b>
43///             { state.height }
44///             <b>{ " Top: " }</b>
45///             { state.top }
46///             <b>{ " Left: " }</b>
47///             { state.left }
48///             <b>{ " Bottom: " }</b>
49///             { state.bottom }
50///             <b>{ " Right: " }</b>
51///             { state.right }
52///         </div>
53///     }
54/// }
55/// ```
56#[hook]
57pub fn use_measure(node: NodeRef) -> UseMeasureState {
58    let state = use_raf_state(UseMeasureState::default);
59
60    {
61        let state = state.clone();
62        use_effect_with(node, move |node| {
63            let closure = Closure::wrap(Box::new(move |entries: Vec<ResizeObserverEntry>| {
64                for entry in &entries {
65                    let rect = entry.content_rect();
66                    state.set(UseMeasureState {
67                        x: rect.x(),
68                        y: rect.y(),
69                        width: rect.width(),
70                        height: rect.height(),
71                        top: rect.top(),
72                        left: rect.left(),
73                        bottom: rect.bottom(),
74                        right: rect.right(),
75                    });
76                }
77            }) as Box<dyn Fn(Vec<ResizeObserverEntry>)>);
78
79            let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()).unwrap_throw();
80            // Forget the closure to keep it alive
81            closure.forget();
82
83            if let Some(element) = &node.cast::<Element>() {
84                observer.observe(element);
85            }
86
87            move || observer.disconnect()
88        });
89    }
90
91    (*state).clone()
92}