12 releases
Uses new Rust 2024
| 0.3.2 | Nov 3, 2025 |
|---|---|
| 0.3.1 | Feb 9, 2025 |
| 0.2.3 | Jan 23, 2025 |
| 0.1.4 | Dec 29, 2024 |
#137 in GUI
135 downloads per month
3MB
4.5K
SLoC
Dioxus Motion 🚀
A lightweight, cross-platform animation library for Dioxus, designed to bring smooth, flexible animations to your Rust web, desktop, and mobile applications.
⚠️ Important Note
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" }
🎯 Live Examples
Visit our Example Website to see these animations in action:
🚀 Page Transitions
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 animation- And more!
- Also, add transitions feature to support page transitions. Example which was translated from router example of Dioxus. More detailed guide will be updated soon.
Quick Value Animation Example
use 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()})"
}
}
}
Animation Sequences Example
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.
✨ Features
- 🔧 Simplified Animatable Trait: Uses standard Rust operators (
+,-,*) instead of custom methods - 🌍 Cross-Platform Support: Works on web, desktop, and mobile
- ⚙️ Flexible Animation Configuration: Spring physics and tween animations
- 📊 Custom Easing Functions: Built-in and custom easing support
- 🧩 Modular Feature Setup: Choose only what you need
- 💡 Simple, Intuitive API: Easy to learn and use
- 🎬 Page Transitions: Smooth route transitions with the
transitionsfeature
🛠 Installation
Add 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"]
🌐 Platform Support
Choose the right feature for your platform:
web: For web applications using WASMdesktop: For desktop and mobile applicationsdefault: Web support (if no feature specified)
🚀 Quick Start
🎨 Creating Custom Animatable Types
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:
- Before: 7 required methods (
zero,epsilon,magnitude,scale,add,sub,interpolate) - After: 2 required methods (
interpolate,magnitude) + standard Rust operators - Result: ~70% less boilerplate, more idiomatic Rust code!
🔄 Migration Guide (v0.3.0)
Breaking Changes
use_motion<T>now requiresT: Send + 'static: Theuse_motion<T>function now requires types to implementSend + 'staticin addition toAnimatable. This enables better thread safety and resource management for animations.
Migration Steps
- Most built-in types (
f32,Transform,Color) already satisfy these bounds - For custom types, ensure they implement
Send + 'static:- Types with non-Send fields (like
Rc<T>) will need to be refactored - Use
Arc<T>instead ofRc<T>for shared ownership in animatable types
- Types with non-Send fields (like
- Minor exports might change so just import
prelude::*if anything breaks on import
use 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();
🔄 Migration Guide (v0.2.0)
Breaking Changes
- Combined
use_value_animationanduse_transform_animationintouse_motion - New animation configuration API
- Updated spring physics parameters
- Changed transform property names
New Animation API
use 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()
}))
);
If you were using transform.get_style(), that function is removed to make the library more generic so I recommend building something like
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...
}
}
🆕 New Features
Loop Modes
.with_loop(LoopMode::Infinite)
.with_loop(LoopMode::Times(3))
Animation Delays
.with_delay(Duration::from_secs(1))
On Complete
.with_on_complete(|| println!("Animation complete!"))
🎓 Advanced Guide: Extending Animations
Implementing the Animatable Trait
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:
Custom Position Type
#[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()
}
}
Best Practices
- Default State: Implement
Defaulttrait for your type's neutral state - Operator Traits: Implement
Add,Sub, andMul<f32>using standard Rust operators - Magnitude: Return the square root of sum of squares for vector types
- Interpolate: Use linear interpolation for smooth transitions
- Epsilon: Uses default 0.01, or override with
with_epsilon()for custom precision
Common Patterns
Circular Values (e.g., angles)
fn 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 }
}
Normalized Values (e.g., colors)
fn scale(&self, factor: f32) -> Self {
Self {
value: (self.value * factor).clamp(0.0, 1.0)
}
}
🌈 Supported Easing Functions
Leverages the easer crate, supporting:
- Linear
- Quadratic
- Cubic
- Quartic
- And more!
🤝 Contributing
- Fork the repository
- Create your feature branch
- Commit changes
- Push to the branch
- Create a Pull Request
📄 License
MIT License
🐞 Reporting Issues
Please report issues on the GitHub repository with:
- Detailed description
- Minimal reproducible example
- Platform and feature configuration used
🌟 Motivation
Bringing elegant, performant motion animations to Rust's web and desktop ecosystems with minimal complexity.
Dependencies
~3–11MB
~229K SLoC