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}