mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-08 19:03:09 -05:00
Refactor/css transition (#128)
* refactor: CSSTransition * fix: CSSTransition timeout processing * fix: CSSTransition repeated processing * fix: Modal css transition
This commit is contained in:
parent
1a55b45d01
commit
d2383445ff
3 changed files with 239 additions and 85 deletions
|
@ -1,8 +1,9 @@
|
||||||
|
use crate::utils::{add_event_listener, EventListenerHandle};
|
||||||
use leptos::{html::ElementDescriptor, *};
|
use leptos::{html::ElementDescriptor, *};
|
||||||
use std::ops::Deref;
|
use std::{ops::Deref, time::Duration};
|
||||||
|
|
||||||
/// # CSS Transition
|
/// # CSS Transition
|
||||||
///
|
///
|
||||||
/// Reference to https://vuejs.org/guide/built-ins/transition.html
|
/// Reference to https://vuejs.org/guide/built-ins/transition.html
|
||||||
#[component]
|
#[component]
|
||||||
pub fn CSSTransition<T, CF, IV>(
|
pub fn CSSTransition<T, CF, IV>(
|
||||||
|
@ -20,91 +21,234 @@ where
|
||||||
IV: IntoView,
|
IV: IntoView,
|
||||||
{
|
{
|
||||||
let display = create_rw_signal((!show.get_untracked()).then_some("display: none;"));
|
let display = create_rw_signal((!show.get_untracked()).then_some("display: none;"));
|
||||||
let remove_class_name = store_value(None::<RemoveClassName>);
|
|
||||||
|
|
||||||
node_ref.on_load(move |node_el| {
|
node_ref.on_load(move |node_el| {
|
||||||
let el = node_el.clone().into_any();
|
let any_el = node_el.clone().into_any();
|
||||||
let el = el.deref();
|
let el = any_el.deref().clone();
|
||||||
let class_list = el.class_list();
|
let class_list = el.class_list();
|
||||||
let remove_class = Callback::new(move |_| {
|
let end_handle = StoredValue::new(None::<EventListenerHandle>);
|
||||||
remove_class_name.update_value(|class| {
|
let end_count = StoredValue::new(None::<usize>);
|
||||||
if let Some(class) = class.take() {
|
let finish = StoredValue::new(None::<Callback<()>>);
|
||||||
match class {
|
|
||||||
RemoveClassName::Enter(active, to) => {
|
let on_end = Callback::new(move |remove: Callback<()>| {
|
||||||
let _ = class_list.remove_2(&active, &to);
|
let Some(CSSTransitionInfo {
|
||||||
if let Some(on_after_enter) = on_after_enter {
|
types,
|
||||||
on_after_enter.call(());
|
prop_count,
|
||||||
}
|
timeout,
|
||||||
}
|
}) = get_transition_info(&el)
|
||||||
RemoveClassName::Leave(active, to) => {
|
else {
|
||||||
let _ = class_list.remove_2(&active, &to);
|
remove.call(());
|
||||||
display.set(Some("display: none;"));
|
return;
|
||||||
if let Some(on_after_leave) = on_after_leave {
|
};
|
||||||
on_after_leave.call(());
|
|
||||||
}
|
finish.set_value(Some(Callback::new(move |_| {
|
||||||
}
|
end_count.set_value(None);
|
||||||
}
|
remove.call(());
|
||||||
|
end_handle.update_value(|h| {
|
||||||
|
h.take().map(|h| {
|
||||||
|
h.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
|
||||||
|
set_timeout(
|
||||||
|
move || {
|
||||||
|
finish.update_value(|v| {
|
||||||
|
v.take().map(|f| f.call(()));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Duration::from_millis(timeout + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
end_count.set_value(Some(0));
|
||||||
|
let event_listener = move || {
|
||||||
|
end_count.update_value(|v| {
|
||||||
|
let Some(v) = v else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*v += 1;
|
||||||
|
});
|
||||||
|
if end_count.with_value(|v| {
|
||||||
|
let Some(v) = v else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
*v >= prop_count
|
||||||
|
}) {
|
||||||
|
finish.update_value(|v| {
|
||||||
|
v.take().map(|f| f.call(()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
let handle = match types {
|
||||||
|
AnimationTypes::Transition => {
|
||||||
|
add_event_listener(any_el.clone(), ev::transitionend, move |_| event_listener())
|
||||||
|
}
|
||||||
|
AnimationTypes::Animation => {
|
||||||
|
add_event_listener(any_el.clone(), ev::animationend, move |_| event_listener())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
end_handle.set_value(Some(handle));
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = node_el
|
let on_finish = move || {
|
||||||
.on(ev::transitionend, move |_| {
|
finish.update_value(|v| {
|
||||||
remove_class.call(());
|
v.take().map(|f| f.call(()));
|
||||||
})
|
|
||||||
.on(ev::animationend, move |_| {
|
|
||||||
remove_class.call(());
|
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
create_render_effect(move |prev: Option<bool>| {
|
let on_enter_fn = {
|
||||||
let show = show.get();
|
let class_list = class_list.clone();
|
||||||
if let Some(node_el) = node_ref.get_untracked() {
|
Callback::new(move |name: String| {
|
||||||
if let Some(prev) = prev {
|
let enter_from = format!("{name}-enter-from");
|
||||||
let name = name.get_untracked();
|
let enter_active = format!("{name}-enter-active");
|
||||||
|
let enter_to = format!("{name}-enter-to");
|
||||||
|
|
||||||
let el = node_el.into_any();
|
let _ = class_list.add_2(&enter_from, &enter_active);
|
||||||
let el = el.deref();
|
display.set(None);
|
||||||
let class_list = el.class_list();
|
|
||||||
|
|
||||||
if show && !prev {
|
let class_list = class_list.clone();
|
||||||
let enter_from = format!("{name}-enter-from");
|
next_frame(move || {
|
||||||
let enter_active = format!("{name}-enter-active");
|
let _ = class_list.remove_1(&enter_from);
|
||||||
let enter_to = format!("{name}-enter-to");
|
let _ = class_list.add_1(&enter_to);
|
||||||
|
|
||||||
let _ = class_list.add_2(&enter_from, &enter_active);
|
let remove = Callback::new(move |_| {
|
||||||
display.set(None);
|
let _ = class_list.remove_2(&enter_active, &enter_to);
|
||||||
request_animation_frame(move || {
|
if let Some(on_after_enter) = on_after_enter {
|
||||||
let _ = class_list.remove_1(&enter_from);
|
on_after_enter.call(());
|
||||||
let _ = class_list.add_1(&enter_to);
|
|
||||||
remove_class_name
|
|
||||||
.set_value(Some(RemoveClassName::Enter(enter_active, enter_to)));
|
|
||||||
if let Some(on_enter) = on_enter {
|
|
||||||
on_enter.call(());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if !show && prev {
|
on_end.call(remove);
|
||||||
let leave_from = format!("{name}-leave-from");
|
|
||||||
let leave_active = format!("{name}-leave-active");
|
|
||||||
let leave_to = format!("{name}-leave-to");
|
|
||||||
|
|
||||||
let _ = class_list.add_2(&leave_from, &leave_active);
|
if let Some(on_enter) = on_enter {
|
||||||
request_animation_frame(move || {
|
on_enter.call(());
|
||||||
let _ = class_list.remove_1(&leave_from);
|
}
|
||||||
let _ = class_list.add_1(&leave_to);
|
});
|
||||||
remove_class_name
|
})
|
||||||
.set_value(Some(RemoveClassName::Leave(leave_active, leave_to)));
|
};
|
||||||
|
|
||||||
|
let on_leave_fn = {
|
||||||
|
let class_list = class_list.clone();
|
||||||
|
Callback::new(move |name: String| {
|
||||||
|
let leave_from = format!("{name}-leave-from");
|
||||||
|
let leave_active = format!("{name}-leave-active");
|
||||||
|
let leave_to = format!("{name}-leave-to");
|
||||||
|
|
||||||
|
let _ = class_list.add_2(&leave_from, &leave_active);
|
||||||
|
|
||||||
|
let class_list = class_list.clone();
|
||||||
|
next_frame(move || {
|
||||||
|
let _ = class_list.remove_1(&leave_from);
|
||||||
|
let _ = class_list.add_1(&leave_to);
|
||||||
|
|
||||||
|
let remove = Callback::new(move |_| {
|
||||||
|
let _ = class_list.remove_2(&leave_active, &leave_to);
|
||||||
|
display.set(Some("display: none;"));
|
||||||
|
if let Some(on_after_leave) = on_after_leave {
|
||||||
|
on_after_leave.call(());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
on_end.call(remove);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
create_render_effect(move |prev: Option<bool>| {
|
||||||
|
let show = show.get();
|
||||||
|
let Some(prev) = prev else {
|
||||||
|
return show;
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = name.get_untracked();
|
||||||
|
|
||||||
|
if show && !prev {
|
||||||
|
on_finish();
|
||||||
|
on_enter_fn.call(name);
|
||||||
|
} else if !show && prev {
|
||||||
|
on_finish();
|
||||||
|
on_leave_fn.call(name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
show
|
show
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
children(display.read_only())
|
children(display.read_only())
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RemoveClassName {
|
fn next_frame(cb: impl FnOnce() + 'static) {
|
||||||
Enter(String, String),
|
request_animation_frame(move || {
|
||||||
Leave(String, String),
|
request_animation_frame(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum AnimationTypes {
|
||||||
|
Transition,
|
||||||
|
Animation,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CSSTransitionInfo {
|
||||||
|
types: AnimationTypes,
|
||||||
|
prop_count: usize,
|
||||||
|
timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transition_info(el: &web_sys::HtmlElement) -> Option<CSSTransitionInfo> {
|
||||||
|
let styles = window().get_computed_style(el).ok().flatten()?;
|
||||||
|
|
||||||
|
let get_style_properties = |property: &str| {
|
||||||
|
styles
|
||||||
|
.get_property_value(property)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(", ")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let transition_delays = get_style_properties("transition-delay");
|
||||||
|
let transition_durations = get_style_properties("transition-duration");
|
||||||
|
let transition_timeout = get_timeout(transition_delays, &transition_durations);
|
||||||
|
let animation_delays = get_style_properties("animation-delay");
|
||||||
|
let animation_durations = get_style_properties("animation-duration");
|
||||||
|
let animation_timeout = get_timeout(animation_delays, &animation_durations);
|
||||||
|
|
||||||
|
let timeout = u64::max(transition_timeout, animation_timeout);
|
||||||
|
let (types, prop_count) = if timeout > 0 {
|
||||||
|
if transition_timeout > animation_timeout {
|
||||||
|
(AnimationTypes::Transition, transition_durations.len())
|
||||||
|
} else {
|
||||||
|
(AnimationTypes::Animation, animation_durations.len())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(CSSTransitionInfo {
|
||||||
|
types,
|
||||||
|
prop_count,
|
||||||
|
timeout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_timeout(mut delays: Vec<String>, durations: &Vec<String>) -> u64 {
|
||||||
|
while delays.len() < durations.len() {
|
||||||
|
delays.append(&mut delays.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_ms(s: &String) -> u64 {
|
||||||
|
if s == "auto" || s.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = s.split_at(s.len() - 1).0;
|
||||||
|
|
||||||
|
(s.parse::<f32>().unwrap_or_default() * 1000.0).floor() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
durations
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, d)| to_ms(d) + to_ms(&delays[i]))
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,15 @@ pub fn Modal(
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
mount_style("modal", include_str!("./modal.css"));
|
mount_style("modal", include_str!("./modal.css"));
|
||||||
|
|
||||||
|
let displayed = RwSignal::new(show.get_untracked());
|
||||||
|
Effect::new(move |prev| {
|
||||||
|
let show = show.get();
|
||||||
|
if prev.is_some() && show {
|
||||||
|
displayed.set(true);
|
||||||
|
}
|
||||||
|
show
|
||||||
|
});
|
||||||
|
|
||||||
let on_mask_click = move |_| {
|
let on_mask_click = move |_| {
|
||||||
if mask_closeable.get_untracked() {
|
if mask_closeable.get_untracked() {
|
||||||
show.set(false);
|
show.set(false);
|
||||||
|
@ -73,15 +82,16 @@ pub fn Modal(
|
||||||
ref=mask_ref
|
ref=mask_ref
|
||||||
></div>
|
></div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
<CSSTransition
|
<div class="thaw-modal-scroll" style=move || (!displayed.get()).then_some("display: none") ref=scroll_ref>
|
||||||
node_ref=scroll_ref
|
<CSSTransition
|
||||||
show=show.signal()
|
node_ref=modal_ref
|
||||||
name="fade-in-scale-up-transition"
|
show=show.signal()
|
||||||
on_enter
|
name="fade-in-scale-up-transition"
|
||||||
let:display
|
on_enter
|
||||||
>
|
on_after_leave=move |_| displayed.set(false)
|
||||||
<div class="thaw-modal-scroll" style=move || display.get() ref=scroll_ref>
|
let:display
|
||||||
<div class="thaw-modal-body" ref=modal_ref role="dialog" aria-modal="true">
|
>
|
||||||
|
<div class="thaw-modal-body" ref=modal_ref role="dialog" aria-modal="true" style=move || display.get()>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader slot>
|
<CardHeader slot>
|
||||||
<span class="thaw-model-title">{move || title.get()}</span>
|
<span class="thaw-model-title">{move || title.get()}</span>
|
||||||
|
@ -102,8 +112,8 @@ pub fn Modal(
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CSSTransition>
|
||||||
</CSSTransition>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,26 +57,26 @@
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-scale-up-transition-leave-active > .thaw-modal-body {
|
.fade-in-scale-up-transition-leave-active.thaw-modal-body {
|
||||||
transform-origin: inherit;
|
transform-origin: inherit;
|
||||||
transition: opacity 0.25s cubic-bezier(0.4, 0, 1, 1),
|
transition: opacity 0.25s cubic-bezier(0.4, 0, 1, 1),
|
||||||
transform 0.25s cubic-bezier(0.4, 0, 1, 1);
|
transform 0.25s cubic-bezier(0.4, 0, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-scale-up-transition-enter-active > .thaw-modal-body {
|
.fade-in-scale-up-transition-enter-active.thaw-modal-body {
|
||||||
transform-origin: inherit;
|
transform-origin: inherit;
|
||||||
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1),
|
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1),
|
||||||
transform 0.25s cubic-bezier(0, 0, 0.2, 1);
|
transform 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-scale-up-transition-enter-from > .thaw-modal-body,
|
.fade-in-scale-up-transition-enter-from.thaw-modal-body,
|
||||||
.fade-in-scale-up-transition-leave-to > .thaw-modal-body {
|
.fade-in-scale-up-transition-leave-to.thaw-modal-body {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.5);
|
transform: scale(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-scale-up-transition-leave-from > .thaw-modal-body,
|
.fade-in-scale-up-transition-leave-from.thaw-modal-body,
|
||||||
.fade-in-scale-up-transition-enter-to > .thaw-modal-body {
|
.fade-in-scale-up-transition-enter-to.thaw-modal-body {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue