diff --git a/src/components/skeletons/delayed_skeleton.rs b/src/components/skeletons/delayed_skeleton.rs new file mode 100644 index 0000000..4a505d3 --- /dev/null +++ b/src/components/skeletons/delayed_skeleton.rs @@ -0,0 +1,49 @@ +use dioxus::prelude::*; + +/// 骨架屏显示前的最小延迟(毫秒) +/// 加载时间低于此值时骨架屏不会显示,避免闪烁 +const SKELETON_DELAY_MS: u32 = 200; + +#[cfg(target_arch = "wasm32")] +async fn sleep_ms(ms: u32) { + use wasm_bindgen::JsCast; + let js_code = format!("new Promise(r => setTimeout(r, {}))", ms); + if let Ok(promise_val) = js_sys::eval(&js_code) { + if let Ok(promise) = promise_val.dyn_into::() { + let _ = wasm_bindgen_futures::JsFuture::from(promise).await; + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +async fn sleep_ms(ms: u32) { + tokio::time::sleep(std::time::Duration::from_millis(ms as u64)).await; +} + +/// 延迟显示的骨架屏包装组件 +/// +/// 骨架屏区域始终占位,但初始时不可见(opacity-0)。 +/// 延迟 SKELETON_DELAY_MS 毫秒后,如果仍在加载,则淡入显示。 +/// 如果加载很快(< 200ms),用户完全看不到骨架屏。 +#[component] +pub fn DelayedSkeleton(children: Element) -> Element { + let mut visible = use_signal(|| false); + + use_effect(move || { + spawn(async move { + sleep_ms(SKELETON_DELAY_MS).await; + visible.set(true); + }); + }); + + rsx! { + div { + class: if visible() { + "opacity-100 transition-opacity duration-150" + } else { + "opacity-0" + }, + {children} + } + } +} diff --git a/src/components/skeletons/mod.rs b/src/components/skeletons/mod.rs index 6fbd950..453fc1f 100644 --- a/src/components/skeletons/mod.rs +++ b/src/components/skeletons/mod.rs @@ -1,4 +1,5 @@ pub mod archive_skeleton; +pub mod delayed_skeleton; pub mod home_skeleton; pub mod post_card_skeleton; pub mod post_detail_skeleton;