Is a Svelte-like generic crossfade animation achievable with Leptos? #1504
Replies: 2 comments 8 replies
-
I'm not familiar with Svelte's animations, but what you're trying to do sounds like it could be done pretty easily with a CSS animation: just toggling a |
Beta Was this translation helpful? Give feedback.
-
@gbj Good day! I've written a more generic approach: use core::ops::Deref;
use leptos::{
html::{Div, ElementDescriptor},
*,
};
use std::{collections::HashMap, hash::Hash, time::Duration};
use web_sys::HtmlElement;
#[derive(Debug)]
pub enum FlipError<T> {
GetReflowTargetFromNodeRefError,
GetHtmlElementFromNodeRefsError(T),
}
struct TransformData<T>
where
T: ElementDescriptor + 'static,
{
node_ref: NodeRef<T>,
position: (f64, f64),
}
impl<T> TransformData<T>
where
T: ElementDescriptor,
{
pub fn node_ref(&self) -> NodeRef<T> {
self.node_ref
}
pub fn position(&self) -> (f64, f64) {
self.position
}
}
pub struct Flipper<T, V, U>
where
V: ElementDescriptor + 'static + Clone + Deref<Target = U>,
T: Hash + Eq + Copy,
U: Deref<Target = HtmlElement>,
{
mapping: HashMap<T, NodeRef<V>>,
flip_initial_state: Option<HashMap<T, TransformData<V>>>,
reflow_target: NodeRef<Div>,
}
impl<T, V, U> Flipper<T, V, U>
where
V: ElementDescriptor + 'static + Clone + Deref<Target = U>,
T: Hash + Eq + Copy,
U: Deref<Target = HtmlElement>,
{
pub fn new(
pairs: impl IntoIterator<Item = (T, NodeRef<V>)>,
reflow_target: NodeRef<Div>,
) -> Self {
Flipper {
mapping: pairs.into_iter().collect(),
flip_initial_state: None,
reflow_target,
}
}
fn get_ids_to_transform_data_from_node_refs(&self) -> HashMap<T, Option<TransformData<V>>> {
self.mapping
.clone()
.into_iter()
.map(|(id, node_ref)| {
let html_element = node_ref.get();
let transform_data = match html_element {
Some(html_element) => {
let bounding_client_rect = html_element.get_bounding_client_rect();
let left = bounding_client_rect.left();
let top = bounding_client_rect.top();
Some(TransformData {
node_ref,
position: (left, top),
})
}
None => None,
};
(id, transform_data)
})
.collect()
}
pub fn prepare_flip(
&mut self,
) -> Result<impl FnOnce() -> Result<(), FlipError<Vec<T>>> + '_, FlipError<Vec<T>>> {
let ids_to_transform_data = self.get_ids_to_transform_data_from_node_refs();
let results = ids_to_transform_data.into_iter().collect::<Vec<_>>();
let failures: Vec<_> = results.iter().filter(|(_, td)| td.is_none()).collect();
if failures.len() > 0 {
let errors: Vec<_> = failures.iter().map(|(id, _)| id.to_owned()).collect();
let error = FlipError::GetHtmlElementFromNodeRefsError(errors);
Err(error)
} else {
let successes = results;
self.flip_initial_state = Some(
successes
.into_iter()
.map(|(id, td)| (id, td.unwrap()))
.collect(),
);
Ok(|| self.flip())
}
}
fn flip(&self) -> Result<(), FlipError<Vec<T>>> {
let ids_to_transform_data = self.get_ids_to_transform_data_from_node_refs();
let results: Vec<_> = ids_to_transform_data.into_iter().collect();
let failures: Vec<_> = results.iter().filter(|(_, td)| td.is_none()).collect();
if failures.len() > 0 {
let errors = failures.iter().map(|(id, _)| id.to_owned()).collect();
let error = FlipError::GetHtmlElementFromNodeRefsError(errors);
Err(error)
} else {
let flip_final_state: HashMap<_, _> = results.into_iter().collect();
let flip_initial_state = &self.flip_initial_state;
flip_initial_state
.as_ref()
.unwrap()
.iter()
.for_each(|(id, td)| {
let (old_left, old_top) = td.position();
let (new_left, new_top) = flip_final_state
.get(id)
.as_ref()
.unwrap()
.as_ref()
.unwrap()
.position();
let left = old_left - new_left;
let top = old_top - new_top;
td.node_ref()
.get()
.unwrap()
.clone()
.style("transform", &format!("translate({left}px, {top}px)"));
});
// Triggering reflow
match self.reflow_target.get() {
Some(elem) => {
elem.offset_width();
}
None => return Err(FlipError::GetReflowTargetFromNodeRefError),
}
flip_final_state.into_iter().for_each(|(_, td)| {
td.as_ref()
.unwrap()
.node_ref()
.get()
.unwrap()
.clone()
.style("transition", "transform 0.6s");
td.as_ref()
.unwrap()
.node_ref()
.get()
.unwrap()
.clone()
.style("transform", "");
set_timeout(
move || {
td.as_ref()
.unwrap()
.node_ref()
.get()
.unwrap()
.clone()
.style("transition", "");
},
Duration::from_millis(600),
);
});
Ok(())
}
}
} Which results in this: crossfade_example.webmWhat it does is take all the node_refs that require moving and animates them with FLIP, as @diversable pointed out. I'm new to open-source in general, but, once things are cleaned up, I wonder if this is worth a crate. Would it be overkill? Many thanks! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello, and a good day!
I am trying to write a Svelte-like crossfade animation in Leptos, and wondering if an implementation using NodeRefs is possible. The intent is to obtain an element's characteristics, place it in the DOM with transitions for position change, and then set the element on the destination.
This is my naive starting point:
I've tried obtaining the HTMLElement and assigning it to the moving div, and NodeRef.clone_from() - is my approach misguided? I still have to dive deeper into Leptos, and this is just a protoype, but I'd like to make sure my attempt is on the right tracks.
Many thanks!
Beta Was this translation helpful? Give feedback.
All reactions