yew_component_size/lib.rs
1#![deny(missing_docs)]
2
3//!  
4//!
5//! A Yew component that emits events when the parent component changes width/height.
6//! Only compatible with Yew using web_sys.
7//!
8//! # Example:
9//! ```rust
10//! let onsize = self.link.callback(|size: ComponentSize| {
11//! // Access to `size.width` and `size.height`
12//! });
13//!
14//! html! {
15//! // Parent that you're tracking the size of must be `position: relative`
16//! <div style="position: relative;">
17//! // ...
18//! <ComponentSizeObserver onsize=onsize />
19//! </div>
20//! }
21//! ```
22//!
23//! # How it works
24//!
25//! This uses a trick borrowed from Svelte where we use an iframe that is positioned absolutely
26//! to fill it's parent element, and then we listen to the resize event of iframe's window.
27//!
28//! _**Note:** This incurs a small cost and so should not be used on a large number of elements at the same time._
29//!
30//! # License
31//!
32//! Licensed under either of
33//!
34//! * Apache License, Version 2.0
35//! ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
36//! * MIT license
37//! ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
38//!
39//! at your option.
40//!
41//! # Contribution
42//!
43//! Unless you explicitly state otherwise, any contribution intentionally submitted
44//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
45//! dual licensed as above, without any additional terms or conditions.
46
47#[cfg(feature = "serde")]
48use serde::{Deserialize, Serialize};
49use wasm_bindgen::{prelude::Closure, JsCast};
50use web_sys::HtmlIFrameElement;
51use yew::{html, Callback, Component, NodeRef, Properties};
52
53const IFRAME_STYLE: &str = "display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;";
54
55/// Yew component to observe changes to the size of the parent element.
56///
57/// See the crate documentation for an example and more information.
58#[derive(Debug)]
59pub struct ComponentSizeObserver {
60 props: Props,
61 iframe_ref: NodeRef,
62 on_resize: Option<Closure<dyn Fn()>>,
63}
64
65/// ComponentSizeObserver properties
66#[derive(Properties, Clone, PartialEq, Debug)]
67pub struct Props {
68 /// A callback that is fired when the component size changes for any reason.
69 pub onsize: Callback<ComponentSize>,
70}
71
72/// A struct containing the width and height of the component
73#[derive(Default, Clone, PartialEq, Debug)]
74#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
75pub struct ComponentSize {
76 /// Width of the component in pixels
77 pub width: f64,
78
79 /// Height of the component in pixels
80 pub height: f64,
81}
82
83impl Component for ComponentSizeObserver {
84 type Message = ();
85 type Properties = Props;
86
87 fn create(props: Self::Properties, _link: yew::ComponentLink<Self>) -> Self {
88 Self {
89 props,
90 iframe_ref: Default::default(),
91 on_resize: None,
92 }
93 }
94
95 fn update(&mut self, _msg: Self::Message) -> yew::ShouldRender {
96 false
97 }
98
99 fn change(&mut self, props: Self::Properties) -> yew::ShouldRender {
100 if self.props != props {
101 self.props = props;
102 self.add_resize_listener();
103 false
104 } else {
105 false
106 }
107 }
108
109 fn view(&self) -> yew::Html {
110 html! {
111 <iframe style=IFRAME_STYLE ref=self.iframe_ref.clone() />
112 }
113 }
114
115 fn rendered(&mut self, first_render: bool) {
116 if first_render {
117 self.add_resize_listener();
118 }
119 }
120}
121
122impl ComponentSizeObserver {
123 fn add_resize_listener(&mut self) {
124 let iframe = self.iframe_ref.cast::<HtmlIFrameElement>().unwrap();
125 let window = iframe.content_window().unwrap();
126
127 let iframe_ref = self.iframe_ref.clone();
128 let size_callback = self.props.onsize.clone();
129 let on_resize = Closure::wrap(Box::new(move || {
130 let iframe = iframe_ref.cast::<HtmlIFrameElement>().unwrap();
131 let bcr = iframe.get_bounding_client_rect();
132 size_callback.emit(ComponentSize {
133 width: bcr.width(),
134 height: bcr.height(),
135 });
136 }) as Box<dyn Fn()>);
137 window.set_onresize(Some(on_resize.as_ref().unchecked_ref()));
138 self.on_resize = Some(on_resize);
139 }
140}