2023-06-11 01:01:54 +01:00
|
|
|
use crate::core::ElementMaybeSignal;
|
|
|
|
use crate::{use_intersection_observer_with_options, UseIntersectionObserverOptions};
|
|
|
|
use default_struct_builder::DefaultBuilder;
|
|
|
|
use leptos::*;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
|
|
|
/// Tracks the visibility of an element within the viewport.
|
|
|
|
///
|
|
|
|
/// ## Demo
|
|
|
|
///
|
|
|
|
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_element_visibility)
|
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
2023-07-03 15:16:22 +01:00
|
|
|
/// # use leptos::html::Div;
|
2023-06-11 01:01:54 +01:00
|
|
|
/// # use leptos_use::use_element_visibility;
|
|
|
|
/// #
|
|
|
|
/// # #[component]
|
2023-07-27 18:06:36 +01:00
|
|
|
/// # fn Demo() -> impl IntoView {
|
|
|
|
/// let el = create_node_ref::<Div>();
|
2023-06-11 01:01:54 +01:00
|
|
|
///
|
2023-07-27 18:06:36 +01:00
|
|
|
/// let is_visible = use_element_visibility(el);
|
2023-06-11 01:01:54 +01:00
|
|
|
///
|
2023-07-27 18:06:36 +01:00
|
|
|
/// view! {
|
2023-06-11 01:01:54 +01:00
|
|
|
/// <div node_ref=el>
|
|
|
|
/// <h1>{is_visible}</h1>
|
|
|
|
/// </div>
|
|
|
|
/// }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
2023-07-14 22:43:19 +01:00
|
|
|
/// ## Server-Side Rendering
|
|
|
|
///
|
|
|
|
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
|
|
|
///
|
2023-06-11 01:01:54 +01:00
|
|
|
/// ## See also
|
|
|
|
///
|
|
|
|
/// * [`use_intersection_observer`]
|
2023-07-27 18:06:36 +01:00
|
|
|
pub fn use_element_visibility<El, T>(target: El) -> Signal<bool>
|
2023-06-11 01:01:54 +01:00
|
|
|
where
|
2023-07-27 18:06:36 +01:00
|
|
|
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
|
2023-06-11 01:01:54 +01:00
|
|
|
T: Into<web_sys::Element> + Clone + 'static,
|
|
|
|
{
|
|
|
|
use_element_visibility_with_options::<El, T, web_sys::Element, web_sys::Element>(
|
|
|
|
target,
|
|
|
|
UseElementVisibilityOptions::default(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn use_element_visibility_with_options<El, T, ContainerEl, ContainerT>(
|
|
|
|
target: El,
|
|
|
|
options: UseElementVisibilityOptions<ContainerEl, ContainerT>,
|
|
|
|
) -> Signal<bool>
|
|
|
|
where
|
2023-07-27 18:06:36 +01:00
|
|
|
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
|
2023-06-11 01:01:54 +01:00
|
|
|
T: Into<web_sys::Element> + Clone + 'static,
|
2023-07-27 18:06:36 +01:00
|
|
|
ContainerEl: Into<ElementMaybeSignal<ContainerT, web_sys::Element>>,
|
2023-06-11 01:01:54 +01:00
|
|
|
ContainerT: Into<web_sys::Element> + Clone + 'static,
|
|
|
|
{
|
2023-07-27 18:06:36 +01:00
|
|
|
let (is_visible, set_visible) = create_signal(false);
|
2023-06-11 01:01:54 +01:00
|
|
|
|
|
|
|
use_intersection_observer_with_options(
|
2023-08-10 21:36:12 +01:00
|
|
|
target.into(),
|
2023-06-11 01:01:54 +01:00
|
|
|
move |entries, _| {
|
2023-08-10 21:36:12 +01:00
|
|
|
// In some circumstances Chrome passes a first (or only) entry which has a zero bounding client rect
|
|
|
|
// and returns `is_intersecting` erroneously as `false`.
|
2023-08-10 21:38:32 +01:00
|
|
|
if let Some(entry) = entries.into_iter().find(|entry| {
|
|
|
|
let rect = entry.bounding_client_rect();
|
|
|
|
rect.width() > 0.0 || rect.height() > 0.0
|
|
|
|
}) {
|
2023-08-10 21:36:12 +01:00
|
|
|
set_visible.set(entry.is_intersecting());
|
|
|
|
}
|
2023-06-11 01:01:54 +01:00
|
|
|
},
|
|
|
|
UseIntersectionObserverOptions::default().root(options.viewport),
|
|
|
|
);
|
|
|
|
|
|
|
|
is_visible.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Options for [`use_element_visibility_with_options`].
|
|
|
|
#[derive(DefaultBuilder)]
|
|
|
|
pub struct UseElementVisibilityOptions<El, T>
|
|
|
|
where
|
2023-07-27 18:06:36 +01:00
|
|
|
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
|
2023-06-11 01:01:54 +01:00
|
|
|
T: Into<web_sys::Element> + Clone + 'static,
|
|
|
|
{
|
|
|
|
/// A `web_sys::Element` or `web_sys::Document` object which is an ancestor of the intended `target`,
|
|
|
|
/// whose bounding rectangle will be considered the viewport.
|
|
|
|
/// Any part of the target not visible in the visible area of the `root` is not considered visible.
|
|
|
|
/// Defaults to `None` (which means the root `document` will be used).
|
|
|
|
/// Please note that setting this to a `Some(document)` may not be supported by all browsers.
|
|
|
|
/// See [Browser Compatibility](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#browser_compatibility)
|
|
|
|
viewport: Option<El>,
|
|
|
|
|
|
|
|
#[builder(skip)]
|
|
|
|
_marker: PhantomData<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for UseElementVisibilityOptions<web_sys::Element, web_sys::Element> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
viewport: None,
|
|
|
|
_marker: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|