| Crates.io | dioxus-motion |
| lib.rs | dioxus-motion |
| version | 0.3.3 |
| created_at | 2024-12-28 02:32:20.70951+00 |
| updated_at | 2026-01-17 03:17:46.512133+00 |
| description | Animations library for Dioxus. |
| homepage | https://wheregmis.github.io |
| repository | https://github.com/wheregmis/dioxus-motion.git |
| max_upload_size | |
| id | 1496826 |
| size | 3,075,446 |
A lightweight, cross-platform animation library for Dioxus, designed to bring smooth, flexible animations to your Rust web, desktop, and mobile applications.
This repository follows Dioxus's main branch for the latest features and improvements. For production use, we recommend using the stable version from crates.io instead of directly depending on the repository.
# Recommended: Stable version from crates.io
dioxus-motion = "0.3.1"
# Development version: Follows Dioxus main branch
dioxus-motion = { git = "https://github.com/wheregmis/dioxus-motion.git", branch = "main" }
Visit our Example Website to see these animations in action:
use dioxus_motion::prelude::*;
#[derive(Routable, Clone, Debug, PartialEq, MotionTransitions )]
#[rustfmt::skip]
enum Route {
#[layout(NavBar)]
#[route("/")]
#[transition(Fade)]
Home {},
#[route("/slide-left")]
#[transition(ZoomIn)]
SlideLeft {},
#[route("/slide-right")]
SlideRight {},
#[route("/slide-up")]
SlideUp {},
#[route("/slide-down")]
SlideDown {},
#[route("/fade")]
Fade {},
#[end_layout]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
And replace all your Outlet::<Route> {} with AnimatedOutlet::<Route> {} and place the layout containing OutletRouter on top with something like this
#[component]
fn NavBar() -> Element {
rsx! {
nav { id: "navbar take it",
Link { to: Route::Home {}, "Home" }
Link { to: Route::SlideLeft {}, "Blog" }
}
AnimatedOutlet::<Route> {}
}
}
Each route can have its own transition effect:
Fade: Smooth opacity transitionZoomIn: Scale and fade combinationSlideLeft: Horizontal slide animationuse dioxus_motion::prelude::*;
#[component]
fn PulseEffect() -> Element {
let scale = use_motion(1.0f32);
use_effect(move || {
scale.animate_to(
1.2,
AnimationConfig::new(AnimationMode::Spring(Spring {
stiffness: 100.0,
damping: 5.0,
mass: 0.5,
velocity: 1.0
}))
.with_loop(LoopMode::Infinite)
);
});
rsx! {
div {
class: "w-20 h-20 bg-blue-500 rounded-full",
style: "transform: scale({scale.get_value()})"
}
}
}
Chain multiple animations together with different configurations:
let scale = use_motion(1.0f32);
// Create a bouncy sequence
let sequence = AnimationSequence::new()
.then(
1.2, // Scale up
AnimationConfig::new(AnimationMode::Spring(Spring {
stiffness: 400.0,
damping: 10.0,
mass: 1.0,
velocity: 5.0,
}))
)
.then(
0.8, // Scale down
AnimationConfig::new(AnimationMode::Spring(Spring {
stiffness: 300.0,
damping: 15.0,
mass: 1.0,
velocity: -2.0,
}))
)
.then(
1.0, // Return to original
AnimationConfig::new(AnimationMode::Spring(Spring::default()))
);
// Start the sequence
scale.animate_sequence(sequence);
// Each step in the sequence can have its own timing, easing, and spring physics configuration. Sequences can also be looped or chained with other animations.
+, -, *) instead of custom methodstransitions featureAdd to your Cargo.toml:
[dependencies]
dioxus-motion = { version = "0.3.0", optional = true, default-features = false }
[features]
default = ["web"]
web = ["dioxus/web", "dioxus-motion/web"]
desktop = ["dioxus/desktop", "dioxus-motion/desktop"]
mobile = ["dioxus/mobile", "dioxus-motion/desktop"]
If you want to use page transiton dependency will look like,
[dependencies]
dioxus-motion = { version = "0.3.0", optional = true, default-features = false }
[features]
default = ["web"]
web = ["dioxus/web", "dioxus-motion/web", "dioxus-motion/transitions"]
desktop = [
"dioxus/desktop",
"dioxus-motion/desktop",
"dioxus-motion/transitions",
]
mobile = ["dioxus/mobile", "dioxus-motion/desktop", "dioxus-motion/transitions"]
Choose the right feature for your platform:
web: For web applications using WASMdesktop: For desktop and mobile applicationsdefault: Web support (if no feature specified)The simplified Animatable trait makes it easy to create custom animatable types:
use dioxus_motion::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq, Default)]
struct Point3D {
x: f32,
y: f32,
z: f32,
}
// Point3D automatically implements Send + 'static since all fields are Send + 'static
// Implement standard Rust operator traits
impl std::ops::Add for Point3D {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
}
impl std::ops::Sub for Point3D {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
}
}
}
impl std::ops::Mul<f32> for Point3D {
type Output = Self;
fn mul(self, factor: f32) -> Self {
Self {
x: self.x * factor,
y: self.y * factor,
z: self.z * factor,
}
}
}
// Implement Animatable with just two methods!
impl Animatable for Point3D {
fn interpolate(&self, target: &Self, t: f32) -> Self {
*self + (*target - *self) * t
}
fn magnitude(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
}
// Now you can animate 3D points!
let mut position = use_motion(Point3D::default());
position.animate_to(
Point3D { x: 10.0, y: 5.0, z: -2.0 },
AnimationConfig::new(AnimationMode::Spring(Spring::default()))
);
Previous vs. New Trait Complexity:
zero, epsilon, magnitude, scale, add, sub, interpolate)interpolate, magnitude) + standard Rust operatorsuse_motion<T> now requires T: Send + 'static: The use_motion<T> function now requires types to implement Send + 'static in addition to Animatable. This enables better thread safety and resource management for animations.f32, Transform, Color) already satisfy these boundsSend + 'static:
Rc<T>) will need to be refactoredArc<T> instead of Rc<T> for shared ownership in animatable typesprelude::* if anything breaks on importuse dioxus_motion::prelude::*;
// ✅ This works - f32 is Send + 'static
let motion = use_motion(0.0f32);
// ✅ This works - custom type with Send + 'static
#[derive(Copy, Clone, Default)]
struct Point { x: f32, y: f32 } // Send + 'static automatically derived
let point_motion = use_motion(Point::default());
// ❌ This won't compile - Rc<T> is not Send
// let bad_motion = use_motion(std::rc::Rc::new(0.0f32));
// ✅ Use Arc<T> instead for shared ownership
// Note: The type inside Arc must implement Animatable
#[derive(Copy, Clone, Default)]
struct SharedValue { value: f32 }
impl std::ops::Add for SharedValue {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { value: self.value + other.value }
}
}
impl std::ops::Sub for SharedValue {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self { value: self.value - other.value }
}
}
impl std::ops::Mul<f32> for SharedValue {
type Output = Self;
fn mul(self, factor: f32) -> Self {
Self { value: self.value * factor }
}
}
impl dioxus_motion::animations::core::Animatable for SharedValue {
fn interpolate(&self, target: &Self, t: f32) -> Self {
Self { value: self.value + (target.value - self.value) * t }
}
fn magnitude(&self) -> f32 {
self.value.abs()
}
}
let shared_motion = use_motion(SharedValue { value: 0.0 });
// ✅ Alternative: Use Arc to share the motion itself (not the value)
let shared_motion_handle = std::sync::Arc::new(use_motion(0.0f32));
// Now you can clone the Arc and share the motion across components
let motion_clone = shared_motion_handle.clone();
use_value_animation and use_transform_animation into use_motionuse dioxus_motion::prelude::*;
// Before (v0.1.x)
let mut motion = use_value_animation(Motion::new(0.0).to(100.0));
// After (v0.2.x)
let mut value = use_motion(0.0f32);
value.animate_to(
100.0,
AnimationConfig::new(AnimationMode::Tween(Tween {
duration: Duration::from_secs(2),
easing: easer::functions::Linear::ease_in_out,
}))
);
// Before (v0.1.x)
let mut transform = use_transform_animation(Transform::default());
// After (v0.2.x)
let mut transform = use_motion(Transform::default());
transform.animate_to(
Transform::new(100.0, 0.0, 1.2, 45.0),
AnimationConfig::new(AnimationMode::Spring(Spring {
stiffness: 100.0,
damping: 10.0,
mass: 1.0,
..Default::default()
}))
);
let transform = use_motion(Transform::default());
let transform_style = use_memo(move || {
format!(
"transform: translate({}px, {}px) scale({}) rotate({}deg);",
transform.get_value().x,
transform.get_value().y,
transform.get_value().scale,
transform.get_value().rotation * 180.0 / std::f32::consts::PI
)
});
// and using the memo in the component
rsx! {
div {
class: "...",
style: "{transform_style.read()}",
// ...rest of component...
}
}
.with_loop(LoopMode::Infinite)
.with_loop(LoopMode::Times(3))
.with_delay(Duration::from_secs(1))
.with_on_complete(|| println!("Animation complete!"))
The Animatable trait allows you to animate any custom type.
Definition of Animatable Trait
pub trait Animatable:
Copy + 'static + Default +
std::ops::Add<Output = Self> +
std::ops::Sub<Output = Self> +
std::ops::Mul<f32, Output = Self>
{
fn interpolate(&self, target: &Self, t: f32) -> Self;
fn magnitude(&self) -> f32;
fn epsilon() -> f32 { 0.01 } // Default implementation
}
Here's how to implement it:
#[derive(Debug, Copy, Clone)]
struct Position {
x: f32,
y: f32,
}
#[derive(Debug, Copy, Clone, PartialEq, Default)]
struct Position {
x: f32,
y: f32,
}
// Implement standard Rust operator traits
impl std::ops::Add for Position {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { x: self.x + other.x, y: self.y + other.y }
}
}
impl std::ops::Sub for Position {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self { x: self.x - other.x, y: self.y - other.y }
}
}
impl std::ops::Mul<f32> for Position {
type Output = Self;
fn mul(self, factor: f32) -> Self {
Self { x: self.x * factor, y: self.y * factor }
}
}
// Implement Animatable with just two methods!
impl Animatable for Position {
fn interpolate(&self, target: &Self, t: f32) -> Self {
*self + (*target - *self) * t
}
fn magnitude(&self) -> f32 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
Default trait for your type's neutral stateAdd, Sub, and Mul<f32> using standard Rust operatorswith_epsilon() for custom precisionfn interpolate(&self, target: &Self, t: f32) -> Self {
let mut diff = target.angle - self.angle;
// Ensure shortest path
if diff > PI { diff -= 2.0 * PI; }
if diff < -PI { diff += 2.0 * PI; }
Self { angle: self.angle + diff * t }
}
fn scale(&self, factor: f32) -> Self {
Self {
value: (self.value * factor).clamp(0.0, 1.0)
}
}
Leverages the easer crate, supporting:
MIT License
Please report issues on the GitHub repository with:
Bringing elegant, performant motion animations to Rust's web and desktop ecosystems with minimal complexity.