yew_hooks/hooks/use_visible.rs
1use wasm_bindgen::{closure::Closure, JsCast};
2use web_sys::{IntersectionObserver, IntersectionObserverEntry};
3use yew::prelude::*;
4
5use super::use_effect_once;
6
7/// Check if an element is visible. Internally, it uses an [`IntersectionObserver`] to receive
8/// notifications from the browser whenever the visibility state of the node changes.
9///
10/// Setting the sticky bit makes this hook disconnect the observer once the element is visible, and
11/// keep the visibility set to `true`, even when it becomes invisible. This is often desired
12/// for lazy-loading components.
13///
14/// # Example
15///
16/// ```rust
17/// # use yew::prelude::*;
18/// #
19/// use yew_hooks::prelude::*;
20///
21/// #[function_component]
22/// fn MyComponent() -> Html {
23/// let node = use_node_ref();
24/// let visible = use_visible(node.clone(), false);
25///
26/// html! {
27/// <div ref={node}>
28/// if visible {
29/// <p>{"I'm visible!"}</p>
30/// } else {
31/// <p>{"I'm invisible!"}</p>
32/// }
33/// </div>
34/// }
35/// }
36/// ```
37#[hook]
38pub fn use_visible(node: NodeRef, sticky: bool) -> bool {
39 // code adapted from:
40 // https://stackoverflow.com/questions/1462138/event-listener-for-when-element-becomes-visible
41 let visible = use_state_eq(|| false);
42 let visible_clone = visible.clone();
43
44 use_effect_once(move || {
45 let closure = Closure::<dyn Fn(Vec<IntersectionObserverEntry>, IntersectionObserver)>::new(
46 move |entries: Vec<IntersectionObserverEntry>, observer: IntersectionObserver| {
47 // determine if any part of this node is visible.
48 let visible = entries.iter().any(|entry| entry.intersection_ratio() > 0.0);
49
50 // if the visibility changed, update the state.
51 visible_clone.set(visible);
52
53 // if this is sticky and it is currently visible, disconnect the observer.
54 if visible && sticky {
55 observer.disconnect();
56 }
57 },
58 )
59 .into_js_value();
60 let observer = IntersectionObserver::new(closure.dyn_ref().unwrap()).unwrap();
61 if let Some(node) = node.get() {
62 observer.observe(node.dyn_ref().unwrap());
63 }
64 move || observer.disconnect()
65 });
66
67 *visible
68}