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}