1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
use crate::utils::Pausable;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
/// Call function on every requestAnimationFrame.
/// With controls of pausing and resuming.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_raf_fn)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_raf_fn;
/// use leptos_use::utils::Pausable;
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let (count, set_count) = create_signal(0);
///
/// let Pausable { pause, resume, is_active } = use_raf_fn(move |_| {
/// set_count.update(|count| *count += 1);
/// });
///
/// view! { <div>Count: { count }</div> }
/// }
/// ```
///
/// You can use `use_raf_fn_with_options` and set `immediate` to `false`. In that case
/// you have to call `resume()` before the `callback` is executed.
///
/// ## Server-Side Rendering
///
/// On the server this does basically nothing. The provided closure will never be called.
pub fn use_raf_fn(
callback: impl Fn(UseRafFnCallbackArgs) + 'static,
) -> Pausable<impl Fn() + Clone, impl Fn() + Clone> {
use_raf_fn_with_options(callback, UseRafFnOptions::default())
}
/// Version of [`use_raf_fn`] that takes a `UseRafFnOptions`. See [`use_raf_fn`] for how to use.
pub fn use_raf_fn_with_options(
callback: impl Fn(UseRafFnCallbackArgs) + 'static,
options: UseRafFnOptions,
) -> Pausable<impl Fn() + Clone, impl Fn() + Clone> {
let UseRafFnOptions { immediate } = options;
let raf_handle = Rc::new(Cell::new(None::<i32>));
let (is_active, set_active) = create_signal(false);
let loop_ref = Rc::new(RefCell::new(Box::new(|_: f64| {}) as Box<dyn Fn(f64)>));
let request_next_frame = {
cfg_if! { if #[cfg(feature = "ssr")] {
move || ()
} else {
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
let loop_ref = Rc::clone(&loop_ref);
let raf_handle = Rc::clone(&raf_handle);
move || {
let loop_ref = Rc::clone(&loop_ref);
raf_handle.set(
window()
.request_animation_frame(
Closure::once_into_js(move |timestamp: f64| {
loop_ref.borrow()(timestamp);
})
.as_ref()
.unchecked_ref(),
)
.ok(),
);
}
}}
};
let loop_fn = {
let request_next_frame = request_next_frame.clone();
let previous_frame_timestamp = Cell::new(0.0_f64);
move |timestamp: f64| {
if !is_active.try_get_untracked().unwrap_or_default() {
return;
}
let prev_timestamp = previous_frame_timestamp.get();
let delta = if prev_timestamp > 0.0 {
timestamp - prev_timestamp
} else {
0.0
};
#[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter();
callback(UseRafFnCallbackArgs { delta, timestamp });
#[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev);
previous_frame_timestamp.set(timestamp);
request_next_frame();
}
};
let _ = loop_ref.replace(Box::new(loop_fn));
let resume = move || {
if !is_active.get_untracked() {
set_active.set(true);
request_next_frame();
}
};
let pause = move || {
set_active.set(false);
let handle = raf_handle.get();
if let Some(handle) = handle {
let _ = window().cancel_animation_frame(handle);
}
raf_handle.set(None);
};
if immediate {
resume();
}
on_cleanup(pause.clone());
Pausable {
resume,
pause,
is_active: is_active.into(),
}
}
/// Options for [`use_raf_fn_with_options`].
#[derive(DefaultBuilder)]
pub struct UseRafFnOptions {
/// Start the requestAnimationFrame loop immediately on creation. Defaults to `true`.
/// If false the loop will only start when you call `resume()`.
immediate: bool,
}
impl Default for UseRafFnOptions {
fn default() -> Self {
Self { immediate: true }
}
}
/// Type of the argument for the callback of [`use_raf_fn`].
pub struct UseRafFnCallbackArgs {
/// Time elapsed between this and the last frame.
pub delta: f64,
/// Time elapsed since the creation of the web page. See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin) Time origin.
pub timestamp: f64,
}